View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.web.struts.action;
17  
18  import java.io.ByteArrayOutputStream;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Enumeration;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Properties;
27  import java.util.Set;
28  
29  import javax.persistence.EntityManagerFactory;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.xml.namespace.QName;
33  
34  import org.apache.commons.collections.MapUtils;
35  import org.apache.commons.lang.ArrayUtils;
36  import org.apache.commons.lang.StringUtils;
37  import org.apache.ojb.broker.OptimisticLockException;
38  import org.apache.struts.action.ActionForm;
39  import org.apache.struts.action.ActionForward;
40  import org.apache.struts.action.ActionMapping;
41  import org.apache.struts.upload.FormFile;
42  import org.kuali.rice.core.api.CoreApiServiceLocator;
43  import org.kuali.rice.core.api.config.property.ConfigurationService;
44  import org.kuali.rice.core.api.util.ConcreteKeyValue;
45  import org.kuali.rice.core.api.util.KeyValue;
46  import org.kuali.rice.core.api.util.RiceConstants;
47  import org.kuali.rice.core.api.util.RiceKeyConstants;
48  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
49  import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
50  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
51  import org.kuali.rice.kew.api.KewApiConstants;
52  import org.kuali.rice.kew.api.KewApiServiceLocator;
53  import org.kuali.rice.kew.api.WorkflowDocument;
54  import org.kuali.rice.kew.api.action.ActionRequest;
55  import org.kuali.rice.kew.api.action.ActionRequestType;
56  import org.kuali.rice.kew.api.action.DocumentActionParameters;
57  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
58  import org.kuali.rice.kew.api.doctype.DocumentType;
59  import org.kuali.rice.kew.api.exception.WorkflowException;
60  import org.kuali.rice.kim.api.KimConstants;
61  import org.kuali.rice.kim.api.group.Group;
62  import org.kuali.rice.kim.api.group.GroupService;
63  import org.kuali.rice.kim.api.identity.Person;
64  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
65  import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
66  import org.kuali.rice.kns.document.MaintenanceDocument;
67  import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
68  import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase;
69  import org.kuali.rice.kns.document.authorization.DocumentPresentationController;
70  import org.kuali.rice.kns.question.ConfirmationQuestion;
71  import org.kuali.rice.kns.question.RecallQuestion;
72  import org.kuali.rice.kns.rule.PromptBeforeValidation;
73  import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
74  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
75  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
76  import org.kuali.rice.kns.service.DataDictionaryService;
77  import org.kuali.rice.kns.service.DocumentHelperService;
78  import org.kuali.rice.kns.service.KNSServiceLocator;
79  import org.kuali.rice.kns.util.KNSGlobalVariables;
80  import org.kuali.rice.kns.util.WebUtils;
81  import org.kuali.rice.kns.web.struts.form.BlankFormFile;
82  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
83  import org.kuali.rice.kns.web.struts.form.KualiForm;
84  import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
85  import org.kuali.rice.krad.UserSession;
86  import org.kuali.rice.krad.UserSessionUtils;
87  import org.kuali.rice.krad.bo.AdHocRoutePerson;
88  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
89  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
90  import org.kuali.rice.krad.bo.Attachment;
91  import org.kuali.rice.krad.bo.DocumentHeader;
92  import org.kuali.rice.krad.bo.Note;
93  import org.kuali.rice.krad.bo.PersistableBusinessObject;
94  import org.kuali.rice.krad.datadictionary.DataDictionary;
95  import org.kuali.rice.krad.document.Document;
96  import org.kuali.rice.krad.document.authorization.PessimisticLock;
97  import org.kuali.rice.krad.exception.AuthorizationException;
98  import org.kuali.rice.krad.exception.DocumentAuthorizationException;
99  import org.kuali.rice.krad.exception.UnknownDocumentIdException;
100 import org.kuali.rice.krad.rules.rule.event.AddAdHocRoutePersonEvent;
101 import org.kuali.rice.krad.rules.rule.event.AddAdHocRouteWorkgroupEvent;
102 import org.kuali.rice.krad.rules.rule.event.AddNoteEvent;
103 import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent;
104 import org.kuali.rice.krad.rules.rule.event.SendAdHocRequestsEvent;
105 import org.kuali.rice.krad.service.AttachmentService;
106 import org.kuali.rice.krad.service.BusinessObjectService;
107 import org.kuali.rice.krad.service.DocumentService;
108 import org.kuali.rice.krad.service.KRADServiceLocator;
109 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
110 import org.kuali.rice.krad.service.KualiRuleService;
111 import org.kuali.rice.krad.service.NoteService;
112 import org.kuali.rice.krad.service.PessimisticLockService;
113 import org.kuali.rice.krad.util.GlobalVariables;
114 import org.kuali.rice.krad.util.KRADConstants;
115 import org.kuali.rice.krad.util.KRADPropertyConstants;
116 import org.kuali.rice.krad.util.KRADUtils;
117 import org.kuali.rice.krad.util.NoteType;
118 import org.kuali.rice.krad.util.ObjectUtils;
119 import org.kuali.rice.krad.util.SessionTicket;
120 import org.kuali.rice.krad.util.UrlFactory;
121 import org.kuali.rice.ksb.api.KsbApiServiceLocator;
122 import org.springmodules.orm.ojb.OjbOperationException;
123 
124 
125 /**
126  * This class handles all of the document handling related actions in terms of passing them from here at a central point to the
127  * distributed transactions that actually implement document handling.
128  */
129 public class KualiDocumentActionBase extends KualiAction {
130     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiDocumentActionBase.class);
131 
132     // COMMAND constants which cause docHandler to load an existing document instead of creating a new one
133     protected static final String[] DOCUMENT_LOAD_COMMANDS = {
134             KewApiConstants.ACTIONLIST_COMMAND,
135             KewApiConstants.DOCSEARCH_COMMAND,
136             KewApiConstants.SUPERUSER_COMMAND,
137             KewApiConstants.HELPDESK_ACTIONLIST_COMMAND};
138 
139     private DataDictionaryService dataDictionaryService;
140     private DocumentHelperService documentHelperService;
141     private DocumentService documentService;
142     private ConfigurationService kualiConfigurationService;
143     private ParameterService parameterService;
144     private PessimisticLockService pessimisticLockService;
145     private KualiRuleService kualiRuleService;
146     private GroupService groupService;
147     private AttachmentService attachmentService;
148     private NoteService noteService;
149     private BusinessObjectAuthorizationService businessObjectAuthorizationService;
150     private BusinessObjectService businessObjectService;
151     private BusinessObjectMetaDataService businessObjectMetaDataService;
152     private EntityManagerFactory entityManagerFactory;
153 
154     @Override
155     protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
156         if (!(form instanceof KualiDocumentFormBase)) {
157             super.checkAuthorization(form, methodToCall);
158         }
159     }
160 
161     /**
162      * Entry point to all actions.
163      * <p/>
164      * NOTE: No need to hook into execute for handling framwork setup anymore. Just implement the methodToCall for the framework
165      * setup, Constants.METHOD_REQUEST_PARAMETER will contain the full parameter, which can be sub stringed for getting framework
166      * parameters.
167      *
168      * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
169      *      javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
170      */
171     @Override
172     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
173         ActionForward returnForward = mapping.findForward(RiceConstants.MAPPING_BASIC);
174 
175         // if found methodToCall, pass control to that method
176         try {
177             returnForward = super.execute(mapping, form, request, response);
178         } catch (OjbOperationException e) {
179             // special handling for OptimisticLockExceptions
180             OjbOperationException ooe = e;
181 
182             Throwable cause = ooe.getCause();
183             if (cause instanceof OptimisticLockException) {
184                 OptimisticLockException ole = (OptimisticLockException) cause;
185                 GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_OPTIMISTIC_LOCK);
186                 logOjbOptimisticLockException(ole);
187             } else {
188                 // if exceptions are from 'save'
189                 throw e;
190             }
191         } finally {
192             if (form instanceof KualiDocumentFormBase) {
193                 ((KualiDocumentFormBase) form).setMessageMapFromPreviousRequest(GlobalVariables.getMessageMap());
194             }
195         }
196 
197         if (form instanceof KualiDocumentFormBase
198                 && ((KualiDocumentFormBase) form).isHasWorkflowDocument()) {
199             KualiDocumentFormBase formBase = (KualiDocumentFormBase) form;
200             Document document = formBase.getDocument();
201 
202             //KULRICE-2210 fix location of document header population
203             WorkflowDocument workflowDocument = formBase.getDocument().getDocumentHeader().getWorkflowDocument();
204             formBase.populateHeaderFields(workflowDocument);
205             formBase.setDocId(document.getDocumentNumber());
206             //End of KULRICE-2210 fix
207 
208             // check to see if document is a pessimistic lock document
209             if (isFormRepresentingLockObject(formBase)) {
210                 // form represents a document using the BO class PessimisticLock so we need to skip the authorizations in the next logic check
211                 if (LOG.isDebugEnabled()) {
212                     LOG.debug("Form " + formBase + " represents a PessimisticLock BO object");
213                 }
214             } else {
215                 // populates authorization-related fields in KualiDocumentFormBase instances, which are derived from
216                 // information which is contained in the form but which may be unavailable until this point
217                 //DocumentAuthorizer documentAuthorizer = KRADServiceLocatorInternal.getDocumentAuthorizationService().getDocumentAuthorizer(document);
218                 //formBase.populateAuthorizationFields(documentAuthorizer);
219                 populateAuthorizationFields(formBase);
220                 populateAdHocActionRequestCodes(formBase);
221 
222                 //set the formBase into userSession if the document is a session document
223                 UserSession userSession = (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY);
224 
225                 if (WebUtils.isDocumentSession(document, formBase)) {
226                     String formKey = formBase.getFormKey();
227                     if (StringUtils.isBlank(formBase.getFormKey()) || userSession.retrieveObject(formBase.getFormKey()) == null) {
228                         // generate doc form key here if it does not exist
229                         formKey = GlobalVariables.getUserSession().addObjectWithGeneratedKey(form);
230                         formBase.setFormKey(formKey);
231                     }
232                 }
233 
234 
235                 // below used by KualiHttpSessionListener to handle lock expiration
236                 request.getSession().setAttribute(KRADConstants.DOCUMENT_HTTP_SESSION_KEY, document.getDocumentNumber());
237                 // set returnToActionList flag, if needed
238                 if ("displayActionListView".equals(formBase.getCommand())) {
239                     formBase.setReturnToActionList(true);
240                 }
241 
242                 String attachmentEnabled =
243                         getKualiConfigurationService().getPropertyValueAsString(KRADConstants.NOTE_ATTACHMENT_ENABLED);
244                 // Override the document entry
245                 if (attachmentEnabled != null) {
246                     // This is a hack for KULRICE-1602 since the document entry is modified by a
247                     // global configuration that overrides the document templates without some sort
248                     // of rules or control
249                     //DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
250                     DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
251 
252                     org.kuali.rice.krad.datadictionary.DocumentEntry entry = dataDictionary.getDocumentEntry(document.getClass().getName());
253                     entry.setAllowsNoteAttachments(Boolean.parseBoolean(attachmentEnabled));
254                 }
255                 //the request attribute will be used in KualiRequestProcess#processActionPerform
256                 if (exitingDocument()) {
257                     request.setAttribute(KRADConstants.EXITING_DOCUMENT, Boolean.TRUE);
258                 }
259 
260                 // pessimistic locking
261                 String methodCalledViaDispatch = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY);
262                 if ((StringUtils.isNotBlank(methodCalledViaDispatch)) && (exitingDocument())) {
263                     GlobalVariables.getUserSession().removeObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY);
264                     attemptLockRelease(document, methodCalledViaDispatch);
265                 }
266                 setupPessimisticLockMessages(document, request);
267                 if (!document.getPessimisticLocks().isEmpty()) {
268                     String warningMinutes = getParameterService().getParameterValueAsString(KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_DETAIL_TYPE, KRADConstants.SESSION_TIMEOUT_WARNING_MESSAGE_TIME_PARM_NM);
269                     request.setAttribute(KRADConstants.SESSION_TIMEOUT_WARNING_MINUTES, warningMinutes);
270                     request.setAttribute(KRADConstants.SESSION_TIMEOUT_WARNING_MILLISECONDS, (request.getSession().getMaxInactiveInterval() - (Integer.valueOf(warningMinutes) * 60)) * 1000);
271                 }
272             }
273             // Pull in the pending action requests for the document and attach them to the form
274             List<ActionRequest> actionRequests = KewApiServiceLocator.getWorkflowDocumentService().getPendingActionRequests(formBase.getDocId());
275             formBase.setActionRequests(actionRequests);
276         }
277 
278 
279 
280         return returnForward;
281     }
282 
283     protected boolean isFormRepresentingLockObject(KualiDocumentFormBase form) throws Exception {
284         if (form instanceof KualiMaintenanceForm) {
285             KualiMaintenanceForm maintForm = (KualiMaintenanceForm) form;
286             if (ObjectUtils.isNotNull(maintForm.getBusinessObjectClassName())) {
287                 return PessimisticLock.class.isAssignableFrom(Class.forName(((KualiMaintenanceForm) form).getBusinessObjectClassName()));
288             }
289         }
290         return false;
291     }
292 
293     protected void attemptLockRelease(Document document, String methodToCall) {
294         if ((document != null) && (!document.getPessimisticLocks().isEmpty())) {
295             releaseLocks(document, methodToCall);
296             // refresh pessimistic locks in case custom add/remove changes were made
297             document.refreshPessimisticLocks();
298         }
299     }
300 
301     protected void releaseLocks(Document document, String methodToCall) {
302         // first check if the method to call is listed as required lock clearing
303         if (document.getLockClearingMethodNames().contains(methodToCall)) {
304             // find all locks for the current user and remove them
305             getPessimisticLockService().releaseAllLocksForUser(document.getPessimisticLocks(), GlobalVariables.getUserSession().getPerson());
306         }
307     }
308 
309     protected void setupPessimisticLockMessages(Document document, HttpServletRequest request) {
310         List<String> lockMessages = new ArrayList<String>();
311         for (PessimisticLock lock : document.getPessimisticLocks()) {
312             // if lock is owned by current user, do not display message for it
313             if (!lock.isOwnedByUser(GlobalVariables.getUserSession().getPerson())) {
314                 lockMessages.add(generatePessimisticLockMessage(lock));
315             }
316         }
317         request.setAttribute(KRADConstants.PESSIMISTIC_LOCK_MESSAGES, lockMessages);
318     }
319 
320     protected String generatePessimisticLockMessage(PessimisticLock lock) {
321         String descriptor = (lock.getLockDescriptor() != null) ? lock.getLockDescriptor() : "";
322         // TODO: this should be pulled into a properties file
323         return "This document currently has a " + descriptor + " lock owned by " + lock.getOwnedByUser().getName() + " as of " + RiceConstants.getDefaultTimeFormat().format(lock.getGeneratedTimestamp()) + " on " + RiceConstants.getDefaultDateFormat().format(lock.getGeneratedTimestamp());
324     }
325 
326 //    private void saveMessages(HttpServletRequest request) {
327 //        if (!GlobalVariables.getMessageList().isEmpty()) {
328 //            request.setAttribute(KRADConstants.GLOBAL_MESSAGES, GlobalVariables.getMessageList());
329 //        }
330 //    }
331 
332     /**
333      * This method may be used to funnel all document handling through, we could do useful things like log and record various
334      * openings and status Additionally it may be nice to have a single dispatcher that can know how to dispatch to a redirect url
335      * for document specific handling but we may not need that as all we should need is the document to be able to load itself based
336      * on document id and then which actionforward or redirect is pertinent for the document type.
337      *
338      * @param mapping
339      * @param form
340      * @param request
341      * @param response
342      * @return ActionForward
343      * @throws Exception
344      */
345     public ActionForward docHandler(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
346         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
347         String command = kualiDocumentFormBase.getCommand();
348 
349         if (kualiDocumentFormBase.getDocId()!= null && getDocumentService().getByDocumentHeaderId(kualiDocumentFormBase.getDocId()) == null) {
350             ConfigurationService kualiConfigurationService = CoreApiServiceLocator.getKualiConfigurationService();
351             StringBuffer sb = new StringBuffer();
352             sb.append(kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_URL_KEY));
353             sb.append(kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_INITIATED_DOCUMENT_URL_KEY));
354             response.sendRedirect(sb.toString());
355             return new ActionForward(KRADConstants.KRAD_INITIATED_DOCUMENT_VIEW_NAME, sb.toString() ,true);
356         }
357         // in all of the following cases we want to load the document
358         if (ArrayUtils.contains(DOCUMENT_LOAD_COMMANDS, command) && kualiDocumentFormBase.getDocId() != null) {
359             loadDocument(kualiDocumentFormBase);
360         } else if (KewApiConstants.INITIATE_COMMAND.equals(command)) {
361             createDocument(kualiDocumentFormBase);
362         } else {
363             LOG.error("docHandler called with invalid parameters");
364             throw new IllegalArgumentException("docHandler called with invalid parameters");
365         }
366 
367         // attach any extra JS from the data dictionary
368         if (LOG.isDebugEnabled()) {
369             LOG.debug("kualiDocumentFormBase.getAdditionalScriptFiles(): " + kualiDocumentFormBase.getAdditionalScriptFiles());
370         }
371         if (kualiDocumentFormBase.getAdditionalScriptFiles().isEmpty()) {
372             KNSDocumentEntry docEntry = (KNSDocumentEntry) getDataDictionaryService().getDataDictionary().getDocumentEntry(kualiDocumentFormBase.getDocument().getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
373             kualiDocumentFormBase.getAdditionalScriptFiles().addAll(docEntry.getWebScriptFiles());
374         }
375         if (KewApiConstants.SUPERUSER_COMMAND.equalsIgnoreCase(command)) {
376             kualiDocumentFormBase.setSuppressAllButtons(true);
377         }
378         return mapping.findForward(RiceConstants.MAPPING_BASIC);
379     }
380 
381     /**
382      * This method loads the document by its provided document header id. This has been abstracted out so that it can be overridden
383      * in children if the need arises.
384      *
385      * @param kualiDocumentFormBase
386      * @throws org.kuali.rice.kew.api.exception.WorkflowException
387      */
388     protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
389         String docId = kualiDocumentFormBase.getDocId();
390         Document doc = null;
391         doc = getDocumentService().getByDocumentHeaderId(docId);
392         if (doc == null) {
393             throw new UnknownDocumentIdException("Document no longer exists.  It may have been cancelled before being saved.");
394         }
395         WorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
396         if (!getDocumentHelperService().getDocumentAuthorizer(doc).canOpen(doc, GlobalVariables.getUserSession().getPerson())) {
397             throw buildAuthorizationException("open", doc);
398         }
399         // re-retrieve the document using the current user's session - remove the system user from the WorkflowDcument object
400         if (workflowDocument != doc.getDocumentHeader().getWorkflowDocument()) {
401             LOG.warn("Workflow document changed via canOpen check");
402             doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
403         }
404         kualiDocumentFormBase.setDocument(doc);
405         WorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
406         kualiDocumentFormBase.setDocTypeName(workflowDoc.getDocumentTypeName());
407 
408         // KualiDocumentFormBase.populate() needs this updated in the session
409         UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDoc);
410     }
411 
412 
413     /**
414      * This method creates a new document of the type specified by the docTypeName property of the given form. This has been
415      * abstracted out so that it can be overridden in children if the need arises.
416      *
417      * @param kualiDocumentFormBase
418      * @throws WorkflowException
419      */
420     protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
421         Document doc = getDocumentService().getNewDocument(kualiDocumentFormBase.getDocTypeName());
422         UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(),
423                 doc.getDocumentHeader().getWorkflowDocument());
424 
425         kualiDocumentFormBase.setDocument(doc);
426         kualiDocumentFormBase.setDocTypeName(doc.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
427     }
428 
429     /**
430      * This method will insert the new ad hoc person from the from into the list of ad hoc person recipients, put a new new record
431      * in place and return like normal.
432      *
433      * @param mapping
434      * @param form
435      * @param request
436      * @param response
437      * @return ActionForward
438      * @throws Exception
439      */
440     public ActionForward insertAdHocRoutePerson(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
441         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
442         Document document = kualiDocumentFormBase.getDocument();
443 
444 
445         // check authorization for adding ad hoc route person
446         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
447         if (!documentAuthorizer.canSendAdHocRequests(document, kualiDocumentFormBase.getNewAdHocRoutePerson().getActionRequested(), GlobalVariables.getUserSession().getPerson())) {
448             throw buildAuthorizationException("ad-hoc route", document);
449         }
450 
451         // check business rules
452         boolean rulePassed = getKualiRuleService().applyRules(new AddAdHocRoutePersonEvent(document, kualiDocumentFormBase.getNewAdHocRoutePerson()));
453 
454         // if the rule evaluation passed, let's add the ad hoc route person
455         if (rulePassed) {
456             // uppercase userid for consistency
457 //            kualiDocumentFormBase.getNewAdHocRoutePerson().setId(StringUtils.upperCase(kualiDocumentFormBase.getNewAdHocRoutePerson().getId()));
458             kualiDocumentFormBase.getNewAdHocRoutePerson().setId(kualiDocumentFormBase.getNewAdHocRoutePerson().getId());
459             kualiDocumentFormBase.getAdHocRoutePersons().add(kualiDocumentFormBase.getNewAdHocRoutePerson());
460             AdHocRoutePerson person = new AdHocRoutePerson();
461             kualiDocumentFormBase.setNewAdHocRoutePerson(person);
462         }
463 
464         return mapping.findForward(RiceConstants.MAPPING_BASIC);
465     }
466 
467     /**
468      * This method will delete one of the ad hoc persons from the list of ad hoc persons to route to based on the line number of the
469      * delete button that was clicked. then it will return to the form.
470      *
471      * @param mapping
472      * @param form
473      * @param request
474      * @param response
475      * @return ActionForward
476      * @throws Exception
477      */
478     public ActionForward deleteAdHocRoutePerson(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
479         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
480 
481 
482         kualiDocumentFormBase.getAdHocRoutePersons().remove(this.getLineToDelete(request));
483         return mapping.findForward(RiceConstants.MAPPING_BASIC);
484     }
485 
486     /**
487      * This method will insert the new ad hoc workgroup into the list of ad hoc workgroup recipients put a nuew record in place and
488      * then return like normal.
489      *
490      * @param mapping
491      * @param form
492      * @param request
493      * @param response
494      * @return ActionForward
495      * @throws Exception
496      */
497     public ActionForward insertAdHocRouteWorkgroup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
498         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
499         Document document = kualiDocumentFormBase.getDocument();
500 
501         // check authorization for add ad hoc route workgroup
502         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
503         if (!documentAuthorizer.canSendAdHocRequests(document, kualiDocumentFormBase.getNewAdHocRouteWorkgroup().getActionRequested(), GlobalVariables.getUserSession().getPerson())) {
504             throw buildAuthorizationException("ad-hoc route", document);
505         }
506 
507         // check business rules
508         boolean rulePassed = getKualiRuleService().applyRules(new AddAdHocRouteWorkgroupEvent(document, kualiDocumentFormBase.getNewAdHocRouteWorkgroup()));
509 
510         // if the rule evaluation passed, let's add the ad hoc route workgroup
511         if (rulePassed) {
512             //fill id if not already filled
513             AdHocRouteWorkgroup newWorkgroup = kualiDocumentFormBase.getNewAdHocRouteWorkgroup();
514             if (newWorkgroup.getId() == null) {
515                 newWorkgroup.setId(KimApiServiceLocator.getGroupService().getGroupByNamespaceCodeAndName(
516                         newWorkgroup.getRecipientNamespaceCode(), newWorkgroup.getRecipientName()).getId());
517             }
518             kualiDocumentFormBase.getAdHocRouteWorkgroups().add(newWorkgroup);
519             AdHocRouteWorkgroup workgroup = new AdHocRouteWorkgroup();
520             kualiDocumentFormBase.setNewAdHocRouteWorkgroup(workgroup);
521         }
522 
523         return mapping.findForward(RiceConstants.MAPPING_BASIC);
524     }
525 
526     /**
527      * This method will delete one of the ad hoc workgroups from the list of ad hoc workgroups to route to based on the line number
528      * of the delete button that was clicked. then it will return
529      *
530      * @param mapping
531      * @param form
532      * @param request
533      * @param response
534      * @return ActionForward
535      * @throws Exception
536      */
537     public ActionForward deleteAdHocRouteWorkgroup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
538         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
539 
540         kualiDocumentFormBase.getAdHocRouteWorkgroups().remove(this.getLineToDelete(request));
541         return mapping.findForward(RiceConstants.MAPPING_BASIC);
542     }
543 
544     public ActionForward sendAdHocRequests(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
545         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
546         Document document = kualiDocumentFormBase.getDocument();
547 
548         boolean rulePassed = getKualiRuleService().applyRules(new SendAdHocRequestsEvent(document));
549 
550         if (rulePassed) {
551             getDocumentService().sendAdHocRequests(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
552             KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SEND_AD_HOC_REQUESTS_SUCCESSFUL);
553         }
554 
555         return mapping.findForward(RiceConstants.MAPPING_BASIC);
556     }
557 
558     /**
559      * This method will reload the document.
560      *
561      * @param mapping
562      * @param form
563      * @param request
564      * @param response
565      * @return ActionForward
566      * @throws Exception
567      */
568     public ActionForward reload(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
569         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
570         Document document = kualiDocumentFormBase.getDocument();
571 
572         // prepare for the reload action - set doc id and command
573         kualiDocumentFormBase.setDocId(document.getDocumentNumber());
574         kualiDocumentFormBase.setCommand(DOCUMENT_LOAD_COMMANDS[1]);
575 
576         // forward off to the doc handler
577         ActionForward actionForward = docHandler(mapping, form, request, response);
578 
579         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_RELOADED);
580         // TODO: remove this when further testing passed
581 //        if (form instanceof KualiDocumentFormBase) {
582 //            UserSession userSession = (UserSession) request.getSession().getAttribute(RiceConstants.USER_SESSION_KEY);
583 //            // force to recreate formkey in execute method
584 //            if (document instanceof SessionDocumentService && userSession.retrieveObject(kualiDocumentFormBase.getFormKey()) != null) {
585 //        	userSession.removeObject(kualiDocumentFormBase.getFormKey());;
586 //            }
587 //        }
588 
589         return actionForward;
590     }
591 
592     /**
593      * This method will save the document, which will then be available via the action list for the person who saved the document.
594      *
595      * @param mapping
596      * @param form
597      * @param request
598      * @param response
599      * @return ActionForward
600      * @throws Exception
601      */
602     public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
603         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
604         doProcessingAfterPost(kualiDocumentFormBase, request);
605         //get any possible changes to to adHocWorkgroups
606         refreshAdHocRoutingWorkgroupLookups(request, kualiDocumentFormBase);
607         Document document = kualiDocumentFormBase.getDocument();
608 
609         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "save", "");
610         if (forward != null) {
611             return forward;
612         }
613 
614         // save in workflow
615         document = getDocumentService().saveDocument(document);
616         ((KualiDocumentFormBase) form).setDocument(document);
617 
618         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SAVED);
619         kualiDocumentFormBase.setAnnotation("");
620 
621         // TODO: remove this when further testing passed
622 //        if (form instanceof KualiDocumentFormBase) {
623 //            UserSession userSession = (UserSession) request.getSession().getAttribute(RiceConstants.USER_SESSION_KEY);
624 //            // force to recreate formkey in execute method
625 //            if (document instanceof SessionDocumentService && userSession.retrieveObject(kualiDocumentFormBase.getFormKey()) != null) {
626 //        	userSession.removeObject(kualiDocumentFormBase.getFormKey());;
627 //            }
628 //        }
629 
630         return mapping.findForward(RiceConstants.MAPPING_BASIC);
631     }
632 
633     /**
634      * Checks if the given value matches patterns that indicate sensitive data and if configured to give a warning for sensitive data will
635      * prompt the user to continue
636      *
637      * @param mapping
638      * @param form
639      * @param request
640      * @param response
641      * @param fieldName  - name of field with value being checked
642      * @param fieldValue - value to check for sensitive data
643      * @param caller     - method that should be called back from question
644      * @param context    - additional context that needs to be passed back with the question response
645      * @return ActionForward which contains the question forward, or basic forward if user select no to prompt, otherwise will return null
646      *         to indicate processing should continue
647      * @throws Exception
648      */
649     protected ActionForward checkAndWarnAboutSensitiveData(ActionMapping mapping, ActionForm form,
650                                                            HttpServletRequest request, HttpServletResponse response, String fieldName, String fieldValue, String caller, String context)
651             throws Exception {
652         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
653         Document document = kualiDocumentFormBase.getDocument();
654 
655         boolean containsSensitiveData = KRADUtils.containsSensitiveDataPatternMatch(fieldValue);
656 
657         // check if warning is configured in which case we will prompt, or if not business rules will thrown an error
658         boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
659                 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
660                 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
661 
662         // determine if the question has been asked yet
663         Map<String, String> ticketContext = new HashMap<String, String>();
664         ticketContext.put(KRADPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
665         ticketContext.put(KRADConstants.CALLING_METHOD, caller);
666         ticketContext.put(KRADPropertyConstants.NAME, fieldName);
667 
668         boolean questionAsked = GlobalVariables.getUserSession().hasMatchingSessionTicket(
669                 KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET, ticketContext);
670 
671         // start in logic for confirming the sensitive data
672         if (containsSensitiveData && warnForSensitiveData && !questionAsked) {
673             Object question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
674             if (question == null || !KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
675 
676                 // question hasn't been asked, prompt to continue
677                 return this.performQuestionWithoutInput(mapping, form, request, response,
678                         KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION, getKualiConfigurationService()
679                         .getPropertyValueAsString(RiceKeyConstants.QUESTION_SENSITIVE_DATA_DOCUMENT),
680                         KRADConstants.CONFIRMATION_QUESTION, caller, context);
681             }
682 
683             Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
684             if (question != null && KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
685                 // if no button clicked just reload the doc
686                 if (ConfirmationQuestion.NO.equals(buttonClicked)) {
687 
688                     return mapping.findForward(RiceConstants.MAPPING_BASIC);
689                 }
690 
691                 // answered yes, create session ticket so we not to ask question again if there are further question requests
692                 SessionTicket ticket = new SessionTicket(KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET);
693                 ticket.setTicketContext(ticketContext);
694                 GlobalVariables.getUserSession().putSessionTicket(ticket);
695             }
696         }
697 
698         // return null to indicate processing should continue (no redirect)
699         return null;
700     }
701 
702     /**
703      * This method will verify that the form is representing a {@link PessimisticLock} object and delete it if possible
704      *
705      * @param mapping
706      * @param form
707      * @param request
708      * @param response
709      * @return ActionForward
710      * @throws Exception
711      */
712     public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
713         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
714         if (isFormRepresentingLockObject(kualiDocumentFormBase)) {
715             String idValue = request.getParameter(KRADPropertyConstants.ID);
716             getPessimisticLockService().delete(idValue);
717             return returnToSender(request, mapping, kualiDocumentFormBase);
718         }
719         throw buildAuthorizationException(KRADConstants.DELETE_METHOD, kualiDocumentFormBase.getDocument());
720     }
721 
722     /**
723      * route the document using the document service
724      *
725      * @param mapping
726      * @param form
727      * @param request
728      * @param response
729      * @return ActionForward
730      * @throws Exception
731      */
732     public ActionForward performRouteReport(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
733         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
734 
735         kualiDocumentFormBase.setDerivedValuesOnForm(request);
736         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
737         if (preRulesForward != null) {
738             return preRulesForward;
739         }
740 
741         Document document = kualiDocumentFormBase.getDocument();
742         // check authorization for reloading document
743         //DocumentActionFlags flags = getDocumentActionFlags(document);
744         if (!kualiDocumentFormBase.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_PERFORM_ROUTE_REPORT)) {
745             throw buildAuthorizationException("perform route report", document);
746         }
747 
748         String backUrlBase = getReturnLocation(request, mapping);
749         String globalVariableFormKey = GlobalVariables.getUserSession().addObjectWithGeneratedKey(form);
750         // setup back form variables
751         request.setAttribute("backUrlBase", backUrlBase);
752         List<KeyValue> backFormParameters = new ArrayList<KeyValue>();
753         backFormParameters.add(new ConcreteKeyValue(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL));
754         backFormParameters.add(new ConcreteKeyValue(KRADConstants.DOC_FORM_KEY, globalVariableFormKey));
755         request.setAttribute("backFormHiddenVariables", backFormParameters);
756 
757         // setup route report form variables
758         request.setAttribute("workflowRouteReportUrl", getKualiConfigurationService().getPropertyValueAsString(
759                 KRADConstants.WORKFLOW_URL_KEY) + "/" + KewApiConstants.DOCUMENT_ROUTING_REPORT_PAGE);
760         List<KeyValue> generalRouteReportFormParameters = new ArrayList<KeyValue>();
761         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.INITIATOR_ID_ATTRIBUTE_NAME, document.getDocumentHeader().getWorkflowDocument().getDocument().getInitiatorPrincipalId()));
762         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.DOCUMENT_TYPE_NAME_ATTRIBUTE_NAME, document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()));
763         // prepareForRouteReport() method should populate document header workflow document application content xml
764         String xml = document.getXmlForRouteReport();
765         if (LOG.isDebugEnabled()) {
766             LOG.debug("XML being used for Routing Report is: " + xml);
767         }
768         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.DOCUMENT_CONTENT_ATTRIBUTE_NAME, xml));
769 
770         // set up the variables for the form if java script is working (includes a close button variable and no back url)
771         List<KeyValue> javaScriptFormParameters = new ArrayList<KeyValue>();
772         javaScriptFormParameters.addAll(generalRouteReportFormParameters);
773         javaScriptFormParameters.add(new ConcreteKeyValue(KewApiConstants.DISPLAY_CLOSE_BUTTON_ATTRIBUTE_NAME, KewApiConstants.DISPLAY_CLOSE_BUTTON_TRUE_VALUE));
774         request.setAttribute("javaScriptFormVariables", javaScriptFormParameters);
775 
776         // set up the variables for the form if java script is NOT working (includes a back url but no close button)
777         List<KeyValue> noJavaScriptFormParameters = new ArrayList<KeyValue>();
778         noJavaScriptFormParameters.addAll(generalRouteReportFormParameters);
779         Properties parameters = new Properties();
780         for (KeyValue pair : backFormParameters) {
781             parameters.put(pair.getKey(), pair.getValue());
782         }
783         noJavaScriptFormParameters.add(new ConcreteKeyValue(KewApiConstants.RETURN_URL_ATTRIBUTE_NAME, UrlFactory.parameterizeUrl(backUrlBase, parameters)));
784         request.setAttribute("noJavaScriptFormVariables", noJavaScriptFormParameters);
785 
786         return mapping.findForward(KRADConstants.MAPPING_ROUTE_REPORT);
787     }
788 
789     /**
790      * route the document using the document service
791      *
792      * @param mapping
793      * @param form
794      * @param request
795      * @param response
796      * @return ActionForward
797      * @throws Exception
798      */
799     public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
800         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
801         doProcessingAfterPost(kualiDocumentFormBase, request);
802 
803         kualiDocumentFormBase.setDerivedValuesOnForm(request);
804         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
805         if (preRulesForward != null) {
806             return preRulesForward;
807         }
808 
809         Document document = kualiDocumentFormBase.getDocument();
810 
811         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "route", "");
812         if (forward != null) {
813             return forward;
814         }
815 
816         document = getDocumentService().routeDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
817         kualiDocumentFormBase.setDocument(document);
818         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL);
819         kualiDocumentFormBase.setAnnotation("");
820 
821 //        GlobalVariables.getUserSession().addObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY,Boolean.TRUE);
822         return mapping.findForward(RiceConstants.MAPPING_BASIC);
823     }
824 
825     /**
826      * Calls the document service to blanket approve the document
827      *
828      * @param mapping
829      * @param form
830      * @param request
831      * @param response
832      * @return ActionForward
833      * @throws Exception
834      */
835     public ActionForward blanketApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
836         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
837         doProcessingAfterPost(kualiDocumentFormBase, request);
838 
839         // KULRICE-7864: blanket approve should not be allowed when adhoc route for completion request is newly added
840         boolean hasPendingAdhocForCompletion = this.hasPendingAdhocForCompletion(kualiDocumentFormBase);
841         if(hasPendingAdhocForCompletion){
842             GlobalVariables.getMessageMap().putError(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME, RiceKeyConstants.ERROR_ADHOC_COMPLETE_BLANKET_APPROVE_NOT_ALLOWED);
843 
844             return mapping.findForward(RiceConstants.MAPPING_BASIC);
845         }
846 
847         kualiDocumentFormBase.setDerivedValuesOnForm(request);
848         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
849         if (preRulesForward != null) {
850             return preRulesForward;
851         }
852 
853         Document document = kualiDocumentFormBase.getDocument();
854 
855         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "blanketApprove", "");
856         if (forward != null) {
857             return forward;
858         }
859 
860         document = getDocumentService().blanketApproveDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
861         kualiDocumentFormBase.setDocument(document);
862         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_APPROVED);
863         kualiDocumentFormBase.setAnnotation("");
864         return returnToSender(request, mapping, kualiDocumentFormBase);
865     }
866 
867     /**
868      * Calls the document service to approve the document
869      *
870      * @param mapping
871      * @param form
872      * @param request
873      * @param response
874      * @return ActionForward
875      * @throws Exception
876      */
877     public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
878         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
879         doProcessingAfterPost(kualiDocumentFormBase, request);
880 
881         kualiDocumentFormBase.setDerivedValuesOnForm(request);
882         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
883         if (preRulesForward != null) {
884             return preRulesForward;
885         }
886 
887         Document document = kualiDocumentFormBase.getDocument();
888 
889         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "approve", "");
890         if (forward != null) {
891             return forward;
892         }
893 
894         document = getDocumentService().approveDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
895         kualiDocumentFormBase.setDocument(document);
896         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_APPROVED);
897         kualiDocumentFormBase.setAnnotation("");
898         return returnToSender(request, mapping, kualiDocumentFormBase);
899     }
900 
901     /**
902      * Calls the document service to disapprove the document
903      *
904      * @param mapping
905      * @param form
906      * @param request
907      * @param response
908      * @return ActionForward
909      * @throws Exception
910      */
911     public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request,
912                                     HttpServletResponse response) throws Exception {
913 
914         ReasonPrompt prompt = new ReasonPrompt(KRADConstants.DOCUMENT_DISAPPROVE_QUESTION, RiceKeyConstants.QUESTION_DISAPPROVE_DOCUMENT, KRADConstants.CONFIRMATION_QUESTION, RiceKeyConstants.ERROR_DOCUMENT_DISAPPROVE_REASON_REQUIRED, KRADConstants.MAPPING_DISAPPROVE, ConfirmationQuestion.NO, RiceKeyConstants.MESSAGE_DISAPPROVAL_NOTE_TEXT_INTRO);
915         ReasonPrompt.Response resp = prompt.ask(mapping, form, request, response);
916 
917         if (resp.forward != null) {
918             return resp.forward;
919         }
920 
921         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
922         doProcessingAfterPost(kualiDocumentFormBase, request);
923         Document document = kualiDocumentFormBase.getDocument();
924         document = getDocumentService().disapproveDocument(document, resp.reason);
925         kualiDocumentFormBase.setDocument(document);
926         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_DISAPPROVED);
927         kualiDocumentFormBase.setAnnotation("");
928 
929         return returnToSender(request, mapping, kualiDocumentFormBase);
930     }
931 
932     /**
933      * Calls the document service to cancel the document
934      *
935      * @param mapping
936      * @param form
937      * @param request
938      * @param response
939      * @return ActionForward
940      * @throws Exception
941      */
942     public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
943         Object question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
944         // this should probably be moved into a private instance variable
945         // logic for cancel question
946         if (question == null) {
947             // ask question if not already asked
948             return this.performQuestionWithoutInput(mapping, form, request, response, KRADConstants.DOCUMENT_CANCEL_QUESTION, getKualiConfigurationService().getPropertyValueAsString(
949                     "document.question.cancel.text"), KRADConstants.CONFIRMATION_QUESTION, KRADConstants.MAPPING_CANCEL, "");
950         } else {
951             Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
952             if ((KRADConstants.DOCUMENT_CANCEL_QUESTION.equals(question)) && ConfirmationQuestion.NO.equals(buttonClicked)) {
953                 // if no button clicked just reload the doc
954                 return mapping.findForward(RiceConstants.MAPPING_BASIC);
955             }
956             // else go to cancel logic below
957         }
958 
959         final KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
960         doProcessingAfterPost(kualiDocumentFormBase, request);
961         // KULRICE-4447 Call cancelDocument() only if the document exists
962         boolean docExists = getDocumentService().documentExists(kualiDocumentFormBase.getDocId());
963         if (docExists) {
964         	Document document = kualiDocumentFormBase.getDocument();
965             document = getDocumentService().cancelDocument(document, kualiDocumentFormBase.getAnnotation());
966             kualiDocumentFormBase.setDocument(document);
967         }
968 
969         return returnToSender(request, mapping, kualiDocumentFormBase);
970     }
971 
972     /**
973      * Calls the document service to disapprove the document
974      *
975      * @param mapping
976      * @param form
977      * @param request
978      * @param response
979      * @return ActionForward
980      * @throws Exception
981      */
982     public ActionForward recall(ActionMapping mapping, ActionForm form, HttpServletRequest request,
983             HttpServletResponse response) throws Exception {
984 
985         ReasonPrompt prompt = new ReasonPrompt(KRADConstants.DOCUMENT_RECALL_QUESTION, RiceKeyConstants.QUESTION_RECALL_DOCUMENT, KRADConstants.RECALL_QUESTION, RiceKeyConstants.ERROR_DOCUMENT_RECALL_REASON_REQUIRED, KRADConstants.MAPPING_RECALL, null, RiceKeyConstants.MESSAGE_RECALL_NOTE_TEXT_INTRO);
986         ReasonPrompt.Response resp = prompt.ask(mapping, form, request, response);
987 
988         if (resp.forward != null) {
989             return resp.forward;
990         }
991 
992         boolean cancel = !((KRADConstants.DOCUMENT_RECALL_QUESTION.equals(resp.question)) && RecallQuestion.RECALL_TO_ACTIONLIST.equals(resp.button));
993 
994         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
995         doProcessingAfterPost(kualiDocumentFormBase, request);
996         Document document = kualiDocumentFormBase.getDocument();
997         document = getDocumentService().recallDocument(document, resp.reason, cancel);
998         kualiDocumentFormBase.setDocument(document);
999 
1000         // just return to doc view
1001         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1002     }
1003 
1004     /**
1005      * Close the document and take the user back to the index; only after asking the user if they want to save the document first.
1006      * Only users who have the "canSave()" permission are given this option.
1007      *
1008      * @param mapping
1009      * @param form
1010      * @param request
1011      * @param response
1012      * @return ActionForward
1013      * @throws Exception
1014      */
1015     public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1016         KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
1017         doProcessingAfterPost(docForm, request);
1018         Document document = docForm.getDocument();
1019         // only want to prompt them to save if they already can save
1020         if (canSave(docForm)) {
1021 
1022             Object question = getQuestion(request);
1023             // logic for close question
1024             if (question == null) {
1025                 // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1026                 // Stash the unconverted values to populate errors if the user elects to save
1027                 saveUnconvertedValuesToSession(request, docForm);
1028 
1029                 // ask question if not already asked
1030                 return this.performQuestionWithoutInput(mapping, form, request, response, KRADConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION, getKualiConfigurationService().getPropertyValueAsString(
1031                         RiceKeyConstants.QUESTION_SAVE_BEFORE_CLOSE), KRADConstants.CONFIRMATION_QUESTION, KRADConstants.MAPPING_CLOSE, "");
1032             } else {
1033                 Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
1034 
1035                 // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1036                 // Side effecting in that it clears the session attribute that holds the unconverted values.
1037                 Map<String, Object> unconvertedValues = restoreUnconvertedValuesFromSession(request, docForm);
1038 
1039                 if ((KRADConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
1040                     // if yes button clicked - save the doc
1041 
1042                     // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1043                     // If there were values that couldn't be converted, we attempt to populate them so that the
1044                     // the appropriate errors get set on those fields
1045                     if (MapUtils.isNotEmpty(unconvertedValues)) {
1046 						for (Map.Entry<String, Object> entry : unconvertedValues.entrySet()) {
1047 						    docForm.populateForProperty(entry.getKey(), entry.getValue(), unconvertedValues);
1048 						}
1049 					}
1050 
1051                     ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "save", "");
1052                     if (forward != null) {
1053                         return forward;
1054                     }
1055 
1056                     document = getDocumentService().saveDocument(document);
1057                     docForm.setDocument(document);
1058                 }
1059                 // else go to close logic below
1060             }
1061         }
1062 
1063         return returnToSender(request, mapping, docForm);
1064     }
1065 
1066     // stash unconvertedValues in the session
1067     private void saveUnconvertedValuesToSession(HttpServletRequest request, KualiDocumentFormBase docForm) {
1068         if (MapUtils.isNotEmpty(docForm.getUnconvertedValues())) {
1069             request.getSession().setAttribute(getUnconvertedValuesSessionAttributeKey(docForm), new HashMap(docForm.getUnconvertedValues()));
1070         }
1071     }
1072 
1073     // SIDE EFFECTING: clears out unconverted values from the Session and restores them to the form
1074     private Map<String, Object> restoreUnconvertedValuesFromSession(HttpServletRequest request,
1075             KualiDocumentFormBase docForm) {// first restore unconvertedValues and clear out of session
1076         Map<String, Object> unconvertedValues =
1077                 (Map<String, Object>)request.getSession().getAttribute(getUnconvertedValuesSessionAttributeKey(docForm));
1078         if (MapUtils.isNotEmpty(unconvertedValues)) {
1079             request.getSession().removeAttribute(getUnconvertedValuesSessionAttributeKey(docForm));
1080             docForm.setUnconvertedValues(unconvertedValues); // setting them here just for good measure
1081         }
1082         return unconvertedValues;
1083     }
1084 
1085     // create the key based on docId for stashing/retrieving unconvertedValues in the session
1086     private String getUnconvertedValuesSessionAttributeKey(KualiDocumentFormBase docForm) {
1087         return "preCloseUnconvertedValues." + docForm.getDocId();
1088     }
1089 
1090     protected boolean canSave(ActionForm form) {
1091         KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
1092         return docForm.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_CAN_SAVE);
1093     }
1094 
1095     protected Object getQuestion(HttpServletRequest request) {
1096         return request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
1097     }
1098 
1099     /**
1100      * call the document service to clear the fyis
1101      *
1102      * @param mapping
1103      * @param form
1104      * @param request
1105      * @param response
1106      * @return ActionForward
1107      * @throws Exception
1108      */
1109     public ActionForward fyi(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1110         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1111         doProcessingAfterPost(kualiDocumentFormBase, request);
1112         Document document = kualiDocumentFormBase.getDocument();
1113         document = getDocumentService().clearDocumentFyi(document, combineAdHocRecipients(kualiDocumentFormBase));
1114         kualiDocumentFormBase.setDocument(document);
1115         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_FYIED);
1116         kualiDocumentFormBase.setAnnotation("");
1117         return returnToSender(request, mapping, kualiDocumentFormBase);
1118     }
1119 
1120     /**
1121      * call the document service to acknowledge
1122      *
1123      * @param mapping
1124      * @param form
1125      * @param request
1126      * @param response
1127      * @return ActionForward
1128      * @throws Exception
1129      */
1130     public ActionForward acknowledge(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1131         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1132         doProcessingAfterPost(kualiDocumentFormBase, request);
1133         Document document = kualiDocumentFormBase.getDocument();
1134         document = getDocumentService().acknowledgeDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
1135         kualiDocumentFormBase.setDocument(document);
1136         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_ACKNOWLEDGED);
1137         kualiDocumentFormBase.setAnnotation("");
1138         return returnToSender(request, mapping, kualiDocumentFormBase);
1139     }
1140 
1141     /**
1142      * redirect to the supervisor functions that exist.
1143      *
1144      * @param mapping
1145      * @param form
1146      * @param request
1147      * @param response
1148      * @return ActionForward
1149      * @throws Exception
1150      */
1151     public ActionForward supervisorFunctions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1152         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1153 
1154 
1155         String workflowSuperUserUrl = getKualiConfigurationService().getPropertyValueAsString(
1156                 KRADConstants.WORKFLOW_URL_KEY) + "/SuperUser.do?methodToCall=displaySuperUserDocument&documentId=" + kualiDocumentFormBase.getDocument().getDocumentHeader().getDocumentNumber();
1157         response.sendRedirect(workflowSuperUserUrl);
1158 
1159         return null;
1160     }
1161 
1162     /**
1163      * Convenience method to combine the two lists of ad hoc recipients into one which should be done before calling any of the
1164      * document service methods that expect a list of ad hoc recipients
1165      *
1166      * @param kualiDocumentFormBase
1167      * @return List
1168      */
1169     protected List<AdHocRouteRecipient> combineAdHocRecipients(KualiDocumentFormBase kualiDocumentFormBase) {
1170         List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>();
1171         adHocRecipients.addAll(kualiDocumentFormBase.getAdHocRoutePersons());
1172         adHocRecipients.addAll(kualiDocumentFormBase.getAdHocRouteWorkgroups());
1173         return adHocRecipients;
1174     }
1175 
1176     /**
1177      * if the action desires to retain error messages generated by the rules framework for save/submit/etc. validation after returning from a lookup.
1178      *
1179      * @see KualiAction#refresh(org.apache.struts.action.ActionMapping,
1180      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1181      */
1182     @Override
1183     public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1184         KualiDocumentFormBase kualiForm = (KualiDocumentFormBase) form;
1185         kualiForm.setDerivedValuesOnForm(request);
1186 
1187         super.refresh(mapping, form, request, response);
1188         refreshAdHocRoutingWorkgroupLookups(request, kualiForm);
1189 
1190         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1191     }
1192 
1193     /**
1194      * special refresh needed to get the workgroups populated correctly when coming back from workgroup lookups
1195      *
1196      * @param request
1197      * @param kualiForm
1198      * @throws WorkflowException
1199      */
1200     @SuppressWarnings("unchecked")
1201     protected void refreshAdHocRoutingWorkgroupLookups(HttpServletRequest request, KualiDocumentFormBase kualiForm) throws WorkflowException {
1202         for (Enumeration<String> i = request.getParameterNames(); i.hasMoreElements();) {
1203             String parameterName = i.nextElement();
1204             if (parameterName.equals("newAdHocRouteWorkgroup.recipientName") && !"".equals(request.getParameter(parameterName))) {
1205                 //check for namespace
1206                 String namespace = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1207                 if (request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode") != null && !"".equals(request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode").trim())) {
1208                     namespace = request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode").trim();
1209                 }
1210                 Group group = getGroupService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(
1211                         parameterName));
1212                 if (group != null) {
1213                     kualiForm.getNewAdHocRouteWorkgroup().setId(group.getId());
1214                     kualiForm.getNewAdHocRouteWorkgroup().setRecipientName(group.getName());
1215                     kualiForm.getNewAdHocRouteWorkgroup().setRecipientNamespaceCode(group.getNamespaceCode());
1216                 } else {
1217                     GlobalVariables.getMessageMap().putError("newAdHocRouteWorkgroup.recipientNamespaceCode", RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_NAMESPACECODE);
1218                     return;
1219                 }
1220             }
1221             if (parameterName.startsWith("adHocRouteWorkgroup[") && !"".equals(request.getParameter(parameterName))) {
1222                 if (parameterName.endsWith(".recipientName")) {
1223                     int lineNumber = Integer.parseInt(StringUtils.substringBetween(parameterName, "[", "]"));
1224                     //check for namespace
1225                     String namespaceParam = "adHocRouteWorkgroup[" + lineNumber + "].recipientNamespaceCode";
1226                     String namespace = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1227                     if (request.getParameter(namespaceParam) != null && !"".equals(request.getParameter(namespaceParam).trim())) {
1228                         namespace = request.getParameter(namespaceParam).trim();
1229                     }
1230                     Group group = getGroupService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(
1231                             parameterName));
1232                     if (group != null) {
1233                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setId(group.getId());
1234                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientName(group.getName());
1235                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientNamespaceCode(group.getNamespaceCode());
1236                     } else {
1237                         GlobalVariables.getMessageMap().putError(namespaceParam, RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_NAMESPACECODE);
1238                         return;
1239                     }
1240                 }
1241             }
1242             /*
1243             if (parameterName.startsWith("newAdHocRouteWorkgroup[") && !"".equals(request.getParameter(parameterName))) {
1244                 if (parameterName.endsWith(".recipientName")) {
1245                     int lineNumber = Integer.parseInt(StringUtils.substringBetween(parameterName, "[", "]"));
1246                   //check for namespace
1247                     String namespaceParam = "newAdHocRouteWorkgroup[" + lineNumber + "].recipientNamespaceCode";
1248                     String namespace = KimApiConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1249                     if (request.getParameter(namespaceParam) != null && !"".equals(request.getParameter(namespaceParam).trim())) {
1250                         namespace = request.getParameter(namespaceParam).trim();
1251                     }
1252                     KimGroup group = getIdentityManagementService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(parameterName));
1253                     if (group != null) {
1254                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setId(group.getGroupId());
1255                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientName(group.getGroupName());
1256                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientNamespaceCode(group.getNamespaceCode());
1257                     } else {
1258                         throw new RuntimeException("Invalid workgroup id passed as parameter.");
1259                     }
1260                 }
1261             }
1262             */
1263         }
1264     }
1265 
1266 
1267     /**
1268      * Cancels the pending attachment, if any.
1269      *
1270      * @param mapping
1271      * @param form
1272      * @param request
1273      * @param response
1274      * @return ActionForward
1275      * @throws Exception
1276      */
1277     public ActionForward cancelBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1278         KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
1279 
1280         // blank current attachmentFile
1281         documentForm.setAttachmentFile(new BlankFormFile());
1282 
1283         // remove current attachment, if any
1284         Note note = documentForm.getNewNote();
1285         note.removeAttachment();
1286         documentForm.setNewNote(note);
1287 
1288         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1289     }
1290 
1291     /**
1292      * Handy method to stream the byte array to response object
1293      *
1294      * @param fileContents
1295      * @param fileName
1296      * @param fileContentType
1297      * @param response
1298      * @throws Exception
1299      */
1300     protected void streamToResponse(byte[] fileContents, String fileName, String fileContentType, HttpServletResponse response) throws Exception {
1301         ByteArrayOutputStream baos = null;
1302         try {
1303             baos = new ByteArrayOutputStream(fileContents.length);
1304             baos.write(fileContents);
1305             WebUtils.saveMimeOutputStreamAsFile(response, fileContentType, baos, fileName);
1306         } finally {
1307             try {
1308                 if (baos != null) {
1309                     baos.close();
1310                     baos = null;
1311                 }
1312             } catch (IOException ioEx) {
1313                 LOG.error("Error while downloading attachment");
1314                 throw new RuntimeException("IOException occurred while downloading attachment", ioEx);
1315             }
1316         }
1317     }
1318 
1319     /**
1320      * Downloads the selected attachment to the user's browser
1321      *
1322      * @param mapping
1323      * @param form
1324      * @param request
1325      * @param response
1326      * @return ActionForward
1327      * @throws Exception
1328      */
1329     public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1330         KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
1331 
1332         int attachmentIndex = selectedAttachmentIndex(request);
1333         if (attachmentIndex >= 0) {
1334             Note note = documentForm.getDocument().getNote(attachmentIndex);
1335             Attachment attachment = note.getAttachment();
1336             //make sure attachment is setup with backwards reference to note (rather then doing this we could also just call the attachment service (with a new method that took in the note)
1337             attachment.setNote(note);
1338 
1339             // since we're downloading a file, all of the editable properties from the previous request will continue to be editable.
1340             documentForm.copyPopulateEditablePropertiesToActionEditableProperties();
1341 
1342             WebUtils.saveMimeInputStreamAsFile(response, attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentContents(), attachment.getAttachmentFileName(), attachment.getAttachmentFileSize().intValue());
1343             return null;
1344         }
1345 
1346         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1347     }
1348 
1349 
1350     /**
1351      * @param request
1352      * @return index of the attachment whose download button was just pressed
1353      */
1354     protected int selectedAttachmentIndex(HttpServletRequest request) {
1355         int attachmentIndex = -1;
1356 
1357         String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
1358         if (StringUtils.isNotBlank(parameterName)) {
1359             String attachmentIndexParam = StringUtils.substringBetween(parameterName, ".attachment[", "].");
1360 
1361             try {
1362                 attachmentIndex = Integer.parseInt(attachmentIndexParam);
1363             } catch (NumberFormatException ignored) {
1364             }
1365         }
1366 
1367         return attachmentIndex;
1368     }
1369 
1370 
1371     /**
1372      * insert a note into the document
1373      *
1374      * @param mapping
1375      * @param form
1376      * @param request
1377      * @param response
1378      * @return ActionForward
1379      * @throws Exception
1380      */
1381     public ActionForward insertBONote(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1382         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1383         Document document = kualiDocumentFormBase.getDocument();
1384         Note newNote = kualiDocumentFormBase.getNewNote();
1385         newNote.setNotePostedTimestampToCurrent();
1386 
1387         String attachmentTypeCode = null;
1388 
1389         FormFile attachmentFile = kualiDocumentFormBase.getAttachmentFile();
1390         if (attachmentFile == null) {
1391             GlobalVariables.getMessageMap().putError(
1392                     String.format("%s.%s",
1393                             KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1394                             KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME),
1395                     RiceKeyConstants.ERROR_UPLOADFILE_NULL);
1396             // This line was removed in order to continue to validates other
1397             // return mapping.findForward(RiceConstants.MAPPING_BASIC);
1398         }
1399 
1400         if (newNote.getAttachment() != null) {
1401             attachmentTypeCode = newNote.getAttachment().getAttachmentTypeCode();
1402         }
1403 
1404         // check authorization for adding notes
1405         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1406         if (!documentAuthorizer.canAddNoteAttachment(document, attachmentTypeCode, GlobalVariables.getUserSession().getPerson())) {
1407             throw buildAuthorizationException("annotate", document);
1408         }
1409 
1410         // create the attachment first, so that failure-to-create-attachment can be treated as a validation failure
1411 
1412         Attachment attachment = null;
1413         if (attachmentFile != null && !StringUtils.isBlank(attachmentFile.getFileName())) {
1414             if (attachmentFile.getFileSize() == 0) {
1415                 GlobalVariables.getMessageMap().putError(
1416                         String.format("%s.%s",
1417                                 KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1418                                 KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME),
1419                         RiceKeyConstants.ERROR_UPLOADFILE_EMPTY,
1420                         attachmentFile.getFileName());
1421                 // This line was removed in order to continue to validates other
1422 //                return mapping.findForward(RiceConstants.MAPPING_BASIC);
1423             } else {
1424                 String attachmentType = null;
1425                 Attachment newAttachment = kualiDocumentFormBase.getNewNote().getAttachment();
1426                 if (newAttachment != null) {
1427                     attachmentType = newAttachment.getAttachmentTypeCode();
1428                 }
1429                 attachment = getAttachmentService().createAttachment(document.getNoteTarget(), attachmentFile.getFileName(), attachmentFile.getContentType(), attachmentFile.getFileSize(), attachmentFile.getInputStream(), attachmentType);
1430             }
1431         }
1432 
1433         DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
1434         org.kuali.rice.krad.datadictionary.DocumentEntry entry = dataDictionary.getDocumentEntry(document.getClass().getName());
1435 
1436         if ( entry != null && entry.getDisplayTopicFieldInNotes()) {
1437             String topicText = kualiDocumentFormBase.getNewNote().getNoteTopicText();
1438             if (StringUtils.isBlank(topicText)) {
1439                 GlobalVariables.getMessageMap().putError(
1440                         String.format("%s.%s",
1441                                 KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1442                                 KRADConstants.NOTE_TOPIC_TEXT_PROPERTY_NAME),
1443                         RiceKeyConstants.ERROR_REQUIRED,
1444                         "Note Topic (Note Topic)");
1445             }
1446         }
1447 
1448         // create a new note from the data passed in
1449         // TODO gah! this is awful
1450         Person kualiUser = GlobalVariables.getUserSession().getPerson();
1451         if (kualiUser == null) {
1452             throw new IllegalStateException("Current UserSession has a null Person.");
1453         }
1454         Note tmpNote = getNoteService().createNote(newNote, document.getNoteTarget(), kualiUser.getPrincipalId());
1455 
1456         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.NOTE, tmpNote.getNoteText(), "insertBONote", "");
1457         if (forward != null) {
1458             return forward;
1459         }
1460 
1461         // validate the note
1462         boolean rulePassed = getKualiRuleService().applyRules(new AddNoteEvent(document, tmpNote));
1463 
1464         // if the rule evaluation passed, let's add the note
1465         if (rulePassed) {
1466             tmpNote.refresh();
1467 
1468 
1469             DocumentHeader documentHeader = document.getDocumentHeader();
1470 
1471             // associate note with object now
1472             document.addNote(tmpNote);
1473 
1474             // persist the note if the document is already saved the getObjectId check is to get around a bug with certain documents where
1475             // "saved" doesn't really persist, if you notice any problems with missing notes check this line
1476             //maintenance document BO note should only be saved into table when document is in the PROCESSED workflow status
1477             if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty(document.getNoteTarget().getObjectId())
1478                     && !(document instanceof MaintenanceDocument && NoteType.BUSINESS_OBJECT.getCode().equals(tmpNote.getNoteTypeCode()))
1479                     ) {
1480                 tmpNote = getNoteService().save(tmpNote);
1481             }
1482             // adding the attachment after refresh gets called, since the attachment record doesn't get persisted
1483             // until the note does (and therefore refresh doesn't have any attachment to autoload based on the id, nor does it
1484             // autopopulate the id since the note hasn't been persisted yet)
1485             if (attachment != null) {
1486                 tmpNote.addAttachment(attachment);
1487                 // save again for attachment, note this is because sometimes the attachment is added first to the above then ojb tries to save
1488                 //without the PK on the attachment I think it is safer then trying to get the sequence manually
1489                 if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty(document.getNoteTarget().getObjectId())
1490                         && !(document instanceof MaintenanceDocument && NoteType.BUSINESS_OBJECT.getCode().equals(tmpNote.getNoteTypeCode()))
1491                         ) {
1492                     tmpNote = getNoteService().save(tmpNote);
1493                 }
1494             }
1495 
1496 
1497             // reset the new note back to an empty one
1498             kualiDocumentFormBase.setNewNote(new Note());
1499         }
1500 
1501 
1502         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1503     }
1504 
1505     /**
1506      * delete a note from the document
1507      *
1508      * @param mapping
1509      * @param form
1510      * @param request
1511      * @param response
1512      * @return ActionForward
1513      * @throws Exception
1514      */
1515     public ActionForward deleteBONote(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1516         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1517         Document document = kualiDocumentFormBase.getDocument();
1518 
1519 
1520 //        DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
1521 //        DocumentEntry entry = dataDictionary.getDocumentEntry(document.getClass().getName());
1522 
1523         // check authorization for adding notes
1524         //DocumentActionFlags flags = getDocumentActionFlags(document);
1525         //if (!kualiDocumentFormBase.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_CAN_ANNOTATE)) {
1526         //    buildAuthorizationException("annotate", document);
1527         //    return mapping.findForward(RiceConstants.MAPPING_BASIC);
1528         //}
1529 
1530         // ok to delete the note/attachment
1531         // derive the note property from the newNote on the form
1532         Note newNote = kualiDocumentFormBase.getNewNote();
1533         Note note = document.getNote(getLineToDelete(request));
1534         Attachment attachment = note.getAttachment();
1535         String attachmentTypeCode = null;
1536         if (attachment != null) {
1537             attachmentTypeCode = attachment.getAttachmentTypeCode();
1538         }
1539         String authorUniversalIdentifier = note.getAuthorUniversalIdentifier();
1540         if (!WebUtils.canDeleteNoteAttachment(document, attachmentTypeCode, authorUniversalIdentifier)) {
1541             throw buildAuthorizationException("annotate", document);
1542         }
1543 
1544         if (attachment != null) { // only do this if the note has been persisted
1545             //KFSMI-798 - refresh() changed to refreshNonUpdateableReferences()
1546             //All references for the business object Attachment are auto-update="none",
1547             //so refreshNonUpdateableReferences() should work the same as refresh()
1548             if (note.getNoteIdentifier() != null) { // KULRICE-2343 don't blow away note reference if the note wasn't persisted
1549                 attachment.refreshNonUpdateableReferences();
1550             }
1551             getAttachmentService().deleteAttachmentContents(attachment);
1552         }
1553         // delete the note if the document is already saved
1554         if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
1555             getNoteService().deleteNote(note);
1556         }
1557         document.removeNote(note);
1558 
1559         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1560     }
1561 
1562     /**
1563      * Override this to customize which routing action to take when sending a note.  This method reads the system parameter
1564      * KR-NS/Document/SEND_NOTE_WORKFLOW_NOTIFICATION_ACTIONS to determine which action to take
1565      *
1566      * @param request
1567      * @param note
1568      * @return a value from {@link KewApiConstants}
1569      */
1570     protected String determineNoteWorkflowNotificationAction(HttpServletRequest request, KualiDocumentFormBase kualiDocumentFormBase, Note note) {
1571         return getParameterService().getParameterValueAsString(KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_DETAIL_TYPE, KRADConstants.SEND_NOTE_WORKFLOW_NOTIFICATION_ACTIONS_PARM_NM);
1572     }
1573 
1574     public ActionForward sendNoteWorkflowNotification(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1575         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1576         Document document = kualiDocumentFormBase.getDocument();
1577 
1578         Note note = document.getNote(getSelectedLine(request));
1579 
1580         // verify recipient was specified
1581         if (StringUtils.isBlank(note.getAdHocRouteRecipient().getId())) {
1582             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.NEW_DOCUMENT_NOTE, RiceKeyConstants.ERROR_SEND_NOTE_NOTIFICATION_RECIPIENT);
1583             return mapping.findForward(RiceConstants.MAPPING_BASIC);
1584         }
1585         // check recipient is valid
1586         else {
1587             note.getAdHocRouteRecipient().setActionRequested(determineNoteWorkflowNotificationAction(request, kualiDocumentFormBase, note));
1588 
1589             boolean rulePassed = getKualiRuleService().applyRules(new AddAdHocRoutePersonEvent(KRADPropertyConstants.NEW_DOCUMENT_NOTE, document, (AdHocRoutePerson) note.getAdHocRouteRecipient()));
1590             if (!rulePassed) {
1591                 return mapping.findForward(RiceConstants.MAPPING_BASIC);
1592             }
1593         }
1594 
1595         // if document is saved, send notification
1596         if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
1597             getDocumentService().sendNoteRouteNotification(document, note, GlobalVariables.getUserSession().getPerson());
1598 
1599             // add success message
1600             KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SEND_NOTE_NOTIFICATION_SUCCESSFUL);
1601         } else {
1602             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.NEW_DOCUMENT_NOTE, RiceKeyConstants.ERROR_SEND_NOTE_NOTIFICATION_DOCSTATUS);
1603         }
1604 
1605         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1606     }
1607 
1608 
1609     /**
1610      * Generates detailed log messages for OptimisticLockExceptions
1611      *
1612      * @param e
1613      */
1614     private final void logOjbOptimisticLockException(OptimisticLockException e) {
1615         if (LOG.isInfoEnabled()) {
1616             StringBuffer message = new StringBuffer("caught OptimisticLockException, caused by ");
1617             Object sourceObject = e.getSourceObject();
1618             String infix = null;
1619             try {
1620                 // try to add instance details
1621                 infix = sourceObject.toString();
1622             } catch (Exception e2) {
1623                 // just use the class name
1624                 infix = sourceObject.getClass().getName();
1625             }
1626             message.append(infix);
1627 
1628             if (sourceObject instanceof PersistableBusinessObject) {
1629                 PersistableBusinessObject persistableObject = (PersistableBusinessObject) sourceObject;
1630                 message.append(" [versionNumber = ").append(persistableObject.getVersionNumber()).append("]");
1631             }
1632 
1633             LOG.info(message.toString(), e);
1634         }
1635     }
1636 
1637 
1638     /**
1639      * Makes calls to the PromptBeforeValidation specified for the document. If the class returns an actionforward, that forward
1640      * will be returned (thus controlling how execution occurs), or null.
1641      *
1642      * @param mapping
1643      * @param form
1644      * @param request
1645      * @param response
1646      * @return
1647      * @throws Exception
1648      */
1649     public ActionForward promptBeforeValidation(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1650         return promptBeforeValidation(mapping, form, request, response, "route");
1651     }
1652 
1653     /**
1654      * Makes calls to the PromptBeforeValidation specified for the document. If the class returns an actionforward, that forward
1655      * will be returned (thus controlling how execution occurs), or null.
1656      *
1657      * @param mapping
1658      * @param form
1659      * @param request
1660      * @param response
1661      * @param methodToCall
1662      * @return
1663      * @throws Exception
1664      */
1665     public ActionForward promptBeforeValidation(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String methodToCall) throws Exception {
1666         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1667 
1668         /* callback to any pre rules check class */
1669         Class<? extends PromptBeforeValidation> promptBeforeValidationClass = getDataDictionaryService().getPromptBeforeValidationClass(kualiDocumentFormBase.getDocTypeName());
1670         if (LOG.isDebugEnabled()) {
1671             LOG.debug("PromptBeforeValidationClass: " + promptBeforeValidationClass);
1672         }
1673         if (promptBeforeValidationClass != null) {
1674             PromptBeforeValidation promptBeforeValidation = promptBeforeValidationClass.newInstance();
1675             PromptBeforeValidationEvent event = new PromptBeforeValidationEvent("Pre Maint route Check", "", kualiDocumentFormBase.getDocument());
1676             boolean continueRoute = promptBeforeValidation.processPrompts(form, request, event);
1677             if (!continueRoute) {
1678                 if (event.isPerformQuestion()) {
1679                     return super.performQuestionWithoutInput(mapping, kualiDocumentFormBase, request, response, event.getQuestionId(), event.getQuestionText(), event.getQuestionType(), methodToCall, event.getQuestionContext());
1680                 } else {
1681                     // This error section is here to avoid a silent and very confusing failure. If the PreRule
1682                     // instance returns a null for the processPreRuleChecks above, but does not set an
1683                     // ActionForwardName on the event, processing will just silently fail here, and the user
1684                     // will be presented with a blank frame.
1685                     //
1686                     // If the processPreRuleCheck() returns a false, an ActionForwardName needs to be set before hand
1687                     // by the PreRule class.
1688                     ActionForward actionForward = mapping.findForward(event.getActionForwardName());
1689                     if (actionForward == null) {
1690                         throw new RuntimeException("No ActionForwardName defined on this Event, no further actions will be processed.");
1691                     }
1692                     return actionForward;
1693                 }
1694             }
1695         }
1696 
1697         return null;
1698     }
1699 
1700 
1701     /**
1702      * Convenience method for building authorization exceptions
1703      *
1704      * @param action
1705      * @param document
1706      */
1707     protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) {
1708         return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), action, document.getDocumentNumber());
1709     }
1710 
1711     protected boolean exitingDocument() {
1712     	String methodCalledViaDispatch = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY);
1713         String methodCompleted = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY);
1714         return StringUtils.isNotEmpty(methodCompleted) && StringUtils.isNotEmpty(methodCalledViaDispatch) && methodCompleted.startsWith(methodCalledViaDispatch);
1715     }
1716 
1717     protected void setupDocumentExit() {
1718     	String methodCalledViaDispatch = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY);
1719     	if(StringUtils.isNotEmpty(methodCalledViaDispatch)) {
1720     		GlobalVariables.getUserSession().addObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY, methodCalledViaDispatch + DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_MARKER);
1721     	}
1722     }
1723 
1724     /**
1725      * If the given form has returnToActionList set to true, this method returns an ActionForward that should take the user back to
1726      * their action list; otherwise, it returns them to the portal.
1727      *
1728      * @param form
1729      * @return
1730      */
1731     protected ActionForward returnToSender(HttpServletRequest request, ActionMapping mapping, KualiDocumentFormBase form) {
1732         final ActionForward dest;
1733         if (form.isReturnToActionList()) {
1734             String workflowBase = getKualiConfigurationService().getPropertyValueAsString(
1735                     KRADConstants.WORKFLOW_URL_KEY);
1736             String actionListUrl = workflowBase + "/ActionList.do";
1737 
1738             dest = new ActionForward(actionListUrl, true);
1739         } else if (StringUtils.isNotBlank(form.getBackLocation())) {
1740             dest = new ActionForward(form.getBackLocation(), true);
1741         } else {
1742             dest = mapping.findForward(KRADConstants.MAPPING_PORTAL);
1743         }
1744 
1745         setupDocumentExit();
1746         return dest;
1747     }
1748 
1749     @SuppressWarnings("unchecked")
1750     protected void populateAuthorizationFields(KualiDocumentFormBase formBase) {
1751         if (formBase.isFormDocumentInitialized()) {
1752             Document document = formBase.getDocument();
1753             Person user = GlobalVariables.getUserSession().getPerson();
1754             DocumentPresentationController documentPresentationController = KNSServiceLocator
1755                     .getDocumentHelperService().getDocumentPresentationController(document);
1756             DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1757             Set<String> documentActions = documentPresentationController.getDocumentActions(document);
1758             documentActions = documentAuthorizer.getDocumentActions(document, user, documentActions);
1759 
1760             if (getDataDictionaryService().getDataDictionary().getDocumentEntry(document.getClass().getName()).getUsePessimisticLocking()) {
1761                 documentActions = getPessimisticLockService().getDocumentActions(document, user, documentActions);
1762             }
1763 
1764             //DocumentActionFlags flags = new DocumentActionFlags();
1765             formBase.setDocumentActions(convertSetToMap(documentActions));
1766 
1767         }
1768     }
1769 
1770     protected void populateAdHocActionRequestCodes(KualiDocumentFormBase formBase) {
1771         Document document = formBase.getDocument();
1772         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1773         Map<String, String> adHocActionRequestCodes = new HashMap<String, String>();
1774 
1775         if (documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_FYI_REQ, GlobalVariables.getUserSession().getPerson())) {
1776             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ_LABEL);
1777         }
1778         if (!document.getDocumentHeader().getWorkflowDocument().isFinal() && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, GlobalVariables.getUserSession().getPerson())) {
1779             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ_LABEL);
1780         }
1781         if (!(document.getDocumentHeader().getWorkflowDocument().isApproved() || document.getDocumentHeader().getWorkflowDocument().isProcessed() || document.getDocumentHeader().getWorkflowDocument().isFinal()) && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, GlobalVariables.getUserSession().getPerson())) {
1782             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ_LABEL);
1783         }
1784 
1785         if ((document.getDocumentHeader().getWorkflowDocument().isInitiated() || document.getDocumentHeader().getWorkflowDocument().isSaved())
1786                 && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, GlobalVariables.getUserSession().getPerson())) {
1787             // Check if there is already a request for completion pending for the document.
1788             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ_LABEL);
1789         }
1790         formBase.setAdHocActionRequestCodes(adHocActionRequestCodes);
1791 
1792     }
1793 
1794 
1795     @SuppressWarnings("unchecked")
1796     protected Map convertSetToMap(Set s) {
1797         Map map = new HashMap();
1798         Iterator i = s.iterator();
1799         while (i.hasNext()) {
1800             Object key = i.next();
1801             map.put(key, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
1802         }
1803         return map;
1804     }
1805 
1806     /**
1807      * @return the dataDictionaryService
1808      */
1809     protected DataDictionaryService getDataDictionaryService() {
1810         if (dataDictionaryService == null) {
1811             dataDictionaryService = KNSServiceLocator.getDataDictionaryService();
1812         }
1813         return dataDictionaryService;
1814     }
1815 
1816     protected DocumentHelperService getDocumentHelperService() {
1817         if (documentHelperService == null) {
1818             documentHelperService = KNSServiceLocator.getDocumentHelperService();
1819         }
1820         return this.documentHelperService;
1821     }
1822 
1823     protected DocumentService getDocumentService() {
1824         if (documentService == null) {
1825             documentService = KRADServiceLocatorWeb.getDocumentService();
1826         }
1827         return this.documentService;
1828     }
1829 
1830     protected ConfigurationService getKualiConfigurationService() {
1831         if (kualiConfigurationService == null) {
1832             kualiConfigurationService = CoreApiServiceLocator.getKualiConfigurationService();
1833         }
1834         return this.kualiConfigurationService;
1835     }
1836 
1837     protected ParameterService getParameterService() {
1838         if (parameterService == null) {
1839             parameterService = CoreFrameworkServiceLocator.getParameterService();
1840         }
1841         return this.parameterService;
1842     }
1843 
1844     protected PessimisticLockService getPessimisticLockService() {
1845         if (pessimisticLockService == null) {
1846             pessimisticLockService = KRADServiceLocatorWeb.getPessimisticLockService();
1847         }
1848         return this.pessimisticLockService;
1849     }
1850 
1851     protected KualiRuleService getKualiRuleService() {
1852         if (kualiRuleService == null) {
1853             kualiRuleService = KRADServiceLocatorWeb.getKualiRuleService();
1854         }
1855         return this.kualiRuleService;
1856     }
1857 
1858     protected GroupService getGroupService() {
1859         if (groupService == null) {
1860             groupService = KimApiServiceLocator.getGroupService();
1861         }
1862         return this.groupService;
1863     }
1864 
1865     protected AttachmentService getAttachmentService() {
1866         if (attachmentService == null) {
1867             attachmentService = KRADServiceLocator.getAttachmentService();
1868         }
1869         return this.attachmentService;
1870     }
1871 
1872     protected NoteService getNoteService() {
1873         if (noteService == null) {
1874             noteService = KRADServiceLocator.getNoteService();
1875         }
1876         return this.noteService;
1877     }
1878 
1879     protected BusinessObjectService getBusinessObjectService() {
1880         if (businessObjectService == null) {
1881             businessObjectService = KNSServiceLocator.getBusinessObjectService();
1882         }
1883         return this.businessObjectService;
1884     }
1885 
1886     @Override
1887     protected BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
1888         if (businessObjectAuthorizationService == null) {
1889             businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
1890         }
1891         return businessObjectAuthorizationService;
1892     }
1893 
1894     public BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
1895         if (businessObjectMetaDataService == null) {
1896             businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
1897         }
1898         return this.businessObjectMetaDataService;
1899     }
1900 
1901     public EntityManagerFactory getEntityManagerFactory() {
1902         if (entityManagerFactory == null) {
1903             entityManagerFactory = KRADServiceLocator.getApplicationEntityManagerFactory();
1904         }
1905         return this.entityManagerFactory;
1906     }
1907 
1908     /**
1909      * @see KualiAction#hideAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1910      */
1911     @Override
1912     public ActionForward hideAllTabs(ActionMapping mapping, ActionForm form,
1913                                      HttpServletRequest request, HttpServletResponse response)
1914             throws Exception {
1915         if (form instanceof KualiDocumentFormBase) {
1916             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1917         }
1918         return super.hideAllTabs(mapping, form, request, response);
1919     }
1920 
1921     /**
1922      * @see KualiAction#showAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1923      */
1924     @Override
1925     public ActionForward showAllTabs(ActionMapping mapping, ActionForm form,
1926                                      HttpServletRequest request, HttpServletResponse response)
1927             throws Exception {
1928         if (form instanceof KualiDocumentFormBase) {
1929             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1930         }
1931         return super.showAllTabs(mapping, form, request, response);
1932     }
1933 
1934     /**
1935      * @see KualiAction#toggleTab(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1936      */
1937     @Override
1938     public ActionForward toggleTab(ActionMapping mapping, ActionForm form,
1939                                    HttpServletRequest request, HttpServletResponse response)
1940             throws Exception {
1941         if (form instanceof KualiDocumentFormBase) {
1942             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1943         }
1944         return super.toggleTab(mapping, form, request, response);
1945     }
1946 
1947     @Override
1948     protected void doProcessingAfterPost(KualiForm form, HttpServletRequest request) {
1949         super.doProcessingAfterPost(form, request);
1950         if (form instanceof KualiDocumentFormBase) {
1951             Document document = ((KualiDocumentFormBase) form).getDocument();
1952 
1953             getBusinessObjectService().linkUserFields(document);
1954         }
1955     }
1956 
1957     /**
1958      * Class that encapsulates the workflow for obtaining an reason from an action prompt.
1959      */
1960     private class ReasonPrompt {
1961         final String questionId;
1962         final String questionTextKey;
1963         final String questionType;
1964         final String missingReasonKey;
1965         final String questionCallerMapping;
1966         final String abortButton;
1967         final String noteIntroKey;
1968 
1969         private class Response {
1970             final String question;
1971             final ActionForward forward;
1972             final String reason;
1973             final String button;
1974             Response(String question, ActionForward forward) {
1975                 this(question, forward, null, null);
1976             }
1977             Response(String question, String reason, String button) {
1978                 this(question, null, reason, button);
1979             }
1980             private Response(String question, ActionForward forward, String reason, String button) {
1981                 this.question = question;
1982                 this.forward = forward;
1983                 this.reason = reason;
1984                 this.button = button;
1985             }
1986         }
1987 
1988         /**
1989          * @param questionId the question id/instance,
1990          * @param questionTextKey application resources key for question text
1991          * @param questionType the {@link org.kuali.rice.kns.question.Question} question type
1992          * @param questionCallerMapping mapping of original action
1993          * @param abortButton button value considered to abort the prompt and return (optional, may be null)
1994          * @param noteIntroKey application resources key for quesiton text prefix (optional, may be null)
1995          */
1996         private ReasonPrompt(String questionId, String questionTextKey, String questionType, String missingReasonKey, String questionCallerMapping, String abortButton, String noteIntroKey) {
1997             this.questionId = questionId;
1998             this.questionTextKey = questionTextKey;
1999             this.questionType = questionType;
2000             this.questionCallerMapping = questionCallerMapping;
2001             this.abortButton = abortButton;
2002             this.noteIntroKey = noteIntroKey;
2003             this.missingReasonKey = missingReasonKey;
2004         }
2005 
2006         /**
2007          * Obtain a validated reason and button value via a Question prompt.  Reason is validated against
2008          * sensitive data patterns, and max Note text length
2009          * @param mapping Struts mapping
2010          * @param form Struts form
2011          * @param request http request
2012          * @param response http response
2013          * @return Response object representing *either*: 1) an ActionForward due to error or abort 2) a reason and button clicked
2014          * @throws Exception
2015          */
2016         public Response ask(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
2017             String question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
2018             String reason = request.getParameter(KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME);
2019 
2020             if (StringUtils.isBlank(reason)) {
2021                 String context = request.getParameter(KRADConstants.QUESTION_CONTEXT);
2022                 if (context != null && StringUtils.contains(context, KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=")) {
2023                     reason = StringUtils.substringAfter(context, KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=");
2024                 }
2025             }
2026 
2027             String disapprovalNoteText = "";
2028 
2029             // start in logic for confirming the disapproval
2030             if (question == null) {
2031                 // ask question if not already asked
2032                 return new Response(question, performQuestionWithInput(mapping, form, request, response,
2033                         this.questionId,
2034                         getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2035                         this.questionType, this.questionCallerMapping, ""));
2036             }
2037 
2038             String buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
2039             if (this.questionId.equals(question) && abortButton != null && abortButton.equals(buttonClicked)) {
2040                 // if no button clicked just reload the doc
2041                 return new Response(question, mapping.findForward(RiceConstants.MAPPING_BASIC));
2042             }
2043 
2044             // have to check length on value entered
2045             String introNoteMessage = "";
2046             if (noteIntroKey != null) {
2047                 introNoteMessage = getKualiConfigurationService().getPropertyValueAsString(this.noteIntroKey) + KRADConstants.BLANK_SPACE;
2048             }
2049 
2050             // build out full message
2051             disapprovalNoteText = introNoteMessage + reason;
2052 
2053             // check for sensitive data in note
2054             boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
2055                     KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
2056                     KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
2057             if (warnForSensitiveData) {
2058                 String context = KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=" + reason;
2059                 ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response,
2060                         KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, disapprovalNoteText, this.questionCallerMapping, context);
2061                 if (forward != null) {
2062                     return new Response(question, forward);
2063                 }
2064             } else {
2065                 if (KRADUtils.containsSensitiveDataPatternMatch(disapprovalNoteText)) {
2066                     return new Response(question, performQuestionWithInputAgainBecauseOfErrors(mapping, form, request, response,
2067                             this.questionId, getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2068                             this.questionType, this.questionCallerMapping, "", reason,
2069                             RiceKeyConstants.ERROR_DOCUMENT_FIELD_CONTAINS_POSSIBLE_SENSITIVE_DATA,
2070                             KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, "reason"));
2071                 }
2072             }
2073 
2074             int disapprovalNoteTextLength = disapprovalNoteText.length();
2075 
2076             // get note text max length from DD
2077             int noteTextMaxLength = getDataDictionaryService().getAttributeMaxLength(Note.class, KRADConstants.NOTE_TEXT_PROPERTY_NAME);
2078 
2079             if (StringUtils.isBlank(reason) || (disapprovalNoteTextLength > noteTextMaxLength)) {
2080 
2081                 if (reason == null) {
2082                     // prevent a NPE by setting the reason to a blank string
2083                     reason = "";
2084                 }
2085                 return new Response(question, performQuestionWithInputAgainBecauseOfErrors(mapping, form, request, response,
2086                         this.questionId,
2087                         getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2088                         this.questionType, this.questionCallerMapping, "", reason,
2089                         this.missingReasonKey,
2090                         KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, Integer.toString(noteTextMaxLength)));
2091             }
2092 
2093             return new Response(question, disapprovalNoteText, buttonClicked);
2094         }
2095     }
2096 
2097     public ActionForward takeSuperUserActions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2098         KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2099         if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2100             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.annotation.missing", "");
2101             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2102         } else if(documentForm.getSelectedActionRequests().isEmpty()) {
2103             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.none.selected", "");
2104             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2105         }  else if (!documentForm.isStateAllowsApproveSingleActionRequest()) {
2106             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.not.allowed", "");
2107             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2108         }
2109 
2110         for(String actionRequestId : documentForm.getSelectedActionRequests()) {
2111             ActionRequest actionRequest = null;
2112             for(ActionRequest pendingActionRequest : documentForm.getActionRequests()) {
2113                 if(StringUtils.equals(pendingActionRequest.getId(), actionRequestId)) {
2114                     actionRequest = pendingActionRequest;
2115                     break;
2116                 }
2117             }
2118             if(actionRequest == null) {
2119                 // If the action request isn't pending then skip it
2120                 continue;
2121             }
2122             if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.COMPLETE.getCode()) ||
2123                 StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.APPROVE.getCode())) {
2124             	Document document = documentForm.getDocument();
2125                 document = getDocumentService().validateAndPersistDocument(document, new RouteDocumentEvent(document));
2126                 documentForm.setDocument(document);
2127             }
2128 
2129             WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2130             DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2131             documentActions.superUserTakeRequestedAction(parameters, true, actionRequestId);
2132             String messageString;
2133             if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.ACKNOWLEDGE.getCode())) {
2134                 messageString = "general.routing.superuser.actionRequestAcknowledged";
2135             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.FYI.getCode())) {
2136                 messageString = "general.routing.superuser.actionRequestFYI";
2137             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.COMPLETE.getCode())) {
2138                 messageString = "general.routing.superuser.actionRequestCompleted";
2139             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.APPROVE.getCode())) {
2140                 messageString = "general.routing.superuser.actionRequestApproved";
2141             } else {
2142                 messageString = "general.routing.superuser.actionRequestApproved";
2143             }
2144             GlobalVariables.getMessageMap().putInfo("document", messageString, documentForm.getDocId(), actionRequestId);
2145         }
2146         documentForm.setSuperUserAnnotation("");
2147         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2148     }
2149 
2150     public ActionForward superUserDisapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2151     	KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2152     	if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2153     		GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.annotation.missing", "");
2154     		return mapping.findForward(RiceConstants.MAPPING_BASIC);
2155     	} else if (!documentForm.getSelectedActionRequests().isEmpty()) {
2156             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.when.actions.checked", "");
2157             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2158         } else if (!documentForm.isStateAllowsApproveOrDisapprove()) {
2159             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.not.allowed", "");
2160             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2161         }
2162 
2163         WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2164         DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2165         documentActions.superUserDisapprove(parameters, true);
2166         GlobalVariables.getMessageMap().putInfo("document", "general.routing.superuser.disapproved", documentForm.getDocId());
2167         documentForm.setSuperUserAnnotation("");
2168         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2169     }
2170 
2171     public ActionForward superUserApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2172         KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2173         if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2174             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.annotation.missing", "");
2175             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2176         } else if (!documentForm.getSelectedActionRequests().isEmpty()) {
2177             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.when.actions.checked", "");
2178             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2179         } else if (!documentForm.isStateAllowsApproveOrDisapprove()) {
2180             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.not.allowed", "");
2181             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2182         }
2183 
2184         WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2185         DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2186         documentActions.superUserBlanketApprove(parameters, true);
2187         GlobalVariables.getMessageMap().putInfo("document", "general.routing.superuser.approved", documentForm.getDocId());
2188         documentForm.setSuperUserAnnotation("");
2189         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2190     }
2191 
2192     private WorkflowDocumentActionsService getWorkflowDocumentActionsService(String documentTypeId) {
2193         DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(documentTypeId);
2194         String applicationId = documentType.getApplicationId();
2195         QName serviceName = new QName(KewApiConstants.Namespaces.KEW_NAMESPACE_2_0,
2196                 KewApiConstants.ServiceNames.WORKFLOW_DOCUMENT_ACTIONS_SERVICE_SOAP);
2197         WorkflowDocumentActionsService service = (WorkflowDocumentActionsService) KsbApiServiceLocator.getServiceBus()
2198                 .getService(serviceName, applicationId);
2199         if (service == null) {
2200             service = KewApiServiceLocator.getWorkflowDocumentActionsService();
2201         }
2202         return service;
2203     }
2204 
2205     /**
2206      * Complete document action
2207      *
2208      * @param mapping
2209      * @param form
2210      * @param request
2211      * @param response
2212      * @return
2213      * @throws Exception
2214      */
2215     public ActionForward complete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
2216         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
2217         doProcessingAfterPost(kualiDocumentFormBase, request);
2218 
2219         kualiDocumentFormBase.setDerivedValuesOnForm(request);
2220         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
2221         if (preRulesForward != null) {
2222             return preRulesForward;
2223         }
2224 
2225         Document document = kualiDocumentFormBase.getDocument();
2226 
2227         document = getDocumentService().completeDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
2228         kualiDocumentFormBase.setDocument(document);
2229         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL);
2230         kualiDocumentFormBase.setAnnotation("");
2231 
2232         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2233     }
2234 
2235     /**
2236      * KULRICE-7864: blanket approve should not be allowed when adhoc route for completion request is newly added
2237      *
2238      * determine whether any adhoc recipient in the given document has been just added for completion action
2239      */
2240     protected boolean hasPendingAdhocForCompletion(KualiDocumentFormBase kualiDocumentFormBase){
2241         List<AdHocRouteRecipient> adHocRecipients = this.combineAdHocRecipients(kualiDocumentFormBase);
2242 
2243         for(AdHocRouteRecipient receipients : adHocRecipients){
2244             String actionRequestedCode = receipients.getActionRequested();
2245 
2246             if(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestedCode)){
2247                 return true;
2248             }
2249         }
2250 
2251         return false;
2252     }
2253 
2254 }
2255