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 org.apache.commons.collections.MapUtils;
19  import org.apache.commons.lang.ArrayUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.ojb.broker.OptimisticLockException;
22  import org.apache.struts.action.ActionForm;
23  import org.apache.struts.action.ActionForward;
24  import org.apache.struts.action.ActionMapping;
25  import org.apache.struts.upload.FormFile;
26  import org.kuali.rice.core.api.CoreApiServiceLocator;
27  import org.kuali.rice.core.api.config.property.ConfigurationService;
28  import org.kuali.rice.core.api.util.ConcreteKeyValue;
29  import org.kuali.rice.core.api.util.KeyValue;
30  import org.kuali.rice.core.api.util.RiceConstants;
31  import org.kuali.rice.core.api.util.RiceKeyConstants;
32  import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
33  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
34  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
35  import org.kuali.rice.kew.api.KewApiServiceLocator;
36  import org.kuali.rice.kew.api.WorkflowDocument;
37  import org.kuali.rice.kew.api.action.ActionRequest;
38  import org.kuali.rice.kew.api.action.ActionRequestType;
39  import org.kuali.rice.kew.api.action.DocumentActionParameters;
40  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
41  import org.kuali.rice.kew.api.doctype.DocumentType;
42  import org.kuali.rice.kew.api.exception.WorkflowException;
43  import org.kuali.rice.kew.api.KewApiConstants;
44  import org.kuali.rice.kim.api.KimConstants;
45  import org.kuali.rice.kim.api.group.Group;
46  import org.kuali.rice.kim.api.group.GroupService;
47  import org.kuali.rice.kim.api.identity.Person;
48  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
49  import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
50  import org.kuali.rice.kns.document.MaintenanceDocument;
51  import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
52  import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase;
53  import org.kuali.rice.kns.document.authorization.DocumentPresentationController;
54  import org.kuali.rice.kns.question.ConfirmationQuestion;
55  import org.kuali.rice.kns.question.RecallQuestion;
56  import org.kuali.rice.kns.rule.PromptBeforeValidation;
57  import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
58  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
59  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
60  import org.kuali.rice.kns.service.DataDictionaryService;
61  import org.kuali.rice.kns.service.DocumentHelperService;
62  import org.kuali.rice.kns.service.KNSServiceLocator;
63  import org.kuali.rice.kns.util.KNSGlobalVariables;
64  import org.kuali.rice.kns.util.WebUtils;
65  import org.kuali.rice.kns.web.struts.form.BlankFormFile;
66  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
67  import org.kuali.rice.kns.web.struts.form.KualiForm;
68  import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
69  import org.kuali.rice.krad.UserSession;
70  import org.kuali.rice.krad.UserSessionUtils;
71  import org.kuali.rice.krad.bo.AdHocRoutePerson;
72  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
73  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
74  import org.kuali.rice.krad.bo.Attachment;
75  import org.kuali.rice.krad.bo.DocumentHeader;
76  import org.kuali.rice.krad.bo.Note;
77  import org.kuali.rice.krad.bo.PersistableBusinessObject;
78  import org.kuali.rice.krad.datadictionary.DataDictionary;
79  import org.kuali.rice.krad.document.Document;
80  import org.kuali.rice.krad.document.authorization.PessimisticLock;
81  import org.kuali.rice.krad.exception.AuthorizationException;
82  import org.kuali.rice.krad.exception.DocumentAuthorizationException;
83  import org.kuali.rice.krad.exception.UnknownDocumentIdException;
84  import org.kuali.rice.krad.rules.rule.event.AddAdHocRoutePersonEvent;
85  import org.kuali.rice.krad.rules.rule.event.AddAdHocRouteWorkgroupEvent;
86  import org.kuali.rice.krad.rules.rule.event.AddNoteEvent;
87  import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent;
88  import org.kuali.rice.krad.rules.rule.event.SendAdHocRequestsEvent;
89  import org.kuali.rice.krad.service.AttachmentService;
90  import org.kuali.rice.krad.service.BusinessObjectService;
91  import org.kuali.rice.krad.service.DocumentService;
92  import org.kuali.rice.krad.service.KRADServiceLocator;
93  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
94  import org.kuali.rice.krad.service.KualiRuleService;
95  import org.kuali.rice.krad.service.NoteService;
96  import org.kuali.rice.krad.service.PessimisticLockService;
97  import org.kuali.rice.krad.util.GlobalVariables;
98  import org.kuali.rice.krad.util.KRADConstants;
99  import org.kuali.rice.krad.util.KRADPropertyConstants;
100 import org.kuali.rice.krad.util.KRADUtils;
101 import org.kuali.rice.krad.util.NoteType;
102 import org.kuali.rice.krad.util.ObjectUtils;
103 import org.kuali.rice.krad.util.SessionTicket;
104 import org.kuali.rice.krad.util.UrlFactory;
105 import org.kuali.rice.ksb.api.KsbApiServiceLocator;
106 import org.springmodules.orm.ojb.OjbOperationException;
107 
108 import javax.persistence.EntityManagerFactory;
109 import javax.servlet.http.HttpServletRequest;
110 import javax.servlet.http.HttpServletResponse;
111 import javax.xml.namespace.QName;
112 
113 import java.io.ByteArrayOutputStream;
114 import java.io.IOException;
115 import java.util.ArrayList;
116 import java.util.Enumeration;
117 import java.util.HashMap;
118 import java.util.Iterator;
119 import java.util.List;
120 import java.util.Map;
121 import java.util.Properties;
122 import java.util.Set;
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 = (org.kuali.rice.krad.datadictionary.DocumentEntry) 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.getLockClearningMethodNames().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         getDocumentService().saveDocument(document);
616 
617         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SAVED);
618         kualiDocumentFormBase.setAnnotation("");
619 
620         // TODO: remove this when further testing passed
621 //        if (form instanceof KualiDocumentFormBase) {
622 //            UserSession userSession = (UserSession) request.getSession().getAttribute(RiceConstants.USER_SESSION_KEY);
623 //            // force to recreate formkey in execute method
624 //            if (document instanceof SessionDocumentService && userSession.retrieveObject(kualiDocumentFormBase.getFormKey()) != null) {
625 //        	userSession.removeObject(kualiDocumentFormBase.getFormKey());;
626 //            }
627 //        }
628 
629         return mapping.findForward(RiceConstants.MAPPING_BASIC);
630     }
631 
632     /**
633      * Checks if the given value matches patterns that indicate sensitive data and if configured to give a warning for sensitive data will
634      * prompt the user to continue
635      *
636      * @param mapping
637      * @param form
638      * @param request
639      * @param response
640      * @param fieldName  - name of field with value being checked
641      * @param fieldValue - value to check for sensitive data
642      * @param caller     - method that should be called back from question
643      * @param context    - additional context that needs to be passed back with the question response
644      * @return ActionForward which contains the question forward, or basic forward if user select no to prompt, otherwise will return null
645      *         to indicate processing should continue
646      * @throws Exception
647      */
648     protected ActionForward checkAndWarnAboutSensitiveData(ActionMapping mapping, ActionForm form,
649                                                            HttpServletRequest request, HttpServletResponse response, String fieldName, String fieldValue, String caller, String context)
650             throws Exception {
651         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
652         Document document = kualiDocumentFormBase.getDocument();
653 
654         boolean containsSensitiveData = KRADUtils.containsSensitiveDataPatternMatch(fieldValue);
655 
656         // check if warning is configured in which case we will prompt, or if not business rules will thrown an error
657         boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
658                 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
659                 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
660 
661         // determine if the question has been asked yet
662         Map<String, String> ticketContext = new HashMap<String, String>();
663         ticketContext.put(KRADPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
664         ticketContext.put(KRADConstants.CALLING_METHOD, caller);
665         ticketContext.put(KRADPropertyConstants.NAME, fieldName);
666 
667         boolean questionAsked = GlobalVariables.getUserSession().hasMatchingSessionTicket(
668                 KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET, ticketContext);
669 
670         // start in logic for confirming the sensitive data
671         if (containsSensitiveData && warnForSensitiveData && !questionAsked) {
672             Object question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
673             if (question == null || !KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
674 
675                 // question hasn't been asked, prompt to continue
676                 return this.performQuestionWithoutInput(mapping, form, request, response,
677                         KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION, getKualiConfigurationService()
678                         .getPropertyValueAsString(RiceKeyConstants.QUESTION_SENSITIVE_DATA_DOCUMENT),
679                         KRADConstants.CONFIRMATION_QUESTION, caller, context);
680             }
681 
682             Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
683             if (question != null && KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
684                 // if no button clicked just reload the doc
685                 if (ConfirmationQuestion.NO.equals(buttonClicked)) {
686 
687                     return mapping.findForward(RiceConstants.MAPPING_BASIC);
688                 }
689 
690                 // answered yes, create session ticket so we not to ask question again if there are further question requests
691                 SessionTicket ticket = new SessionTicket(KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET);
692                 ticket.setTicketContext(ticketContext);
693                 GlobalVariables.getUserSession().putSessionTicket(ticket);
694             }
695         }
696 
697         // return null to indicate processing should continue (no redirect)
698         return null;
699     }
700 
701     /**
702      * This method will verify that the form is representing a {@link PessimisticLock} object and delete it if possible
703      *
704      * @param mapping
705      * @param form
706      * @param request
707      * @param response
708      * @return ActionForward
709      * @throws Exception
710      */
711     public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
712         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
713         if (isFormRepresentingLockObject(kualiDocumentFormBase)) {
714             String idValue = request.getParameter(KRADPropertyConstants.ID);
715             getPessimisticLockService().delete(idValue);
716             return returnToSender(request, mapping, kualiDocumentFormBase);
717         }
718         throw buildAuthorizationException(KRADConstants.DELETE_METHOD, kualiDocumentFormBase.getDocument());
719     }
720 
721     /**
722      * route the document using the document service
723      *
724      * @param mapping
725      * @param form
726      * @param request
727      * @param response
728      * @return ActionForward
729      * @throws Exception
730      */
731     public ActionForward performRouteReport(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
732         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
733 
734         kualiDocumentFormBase.setDerivedValuesOnForm(request);
735         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
736         if (preRulesForward != null) {
737             return preRulesForward;
738         }
739 
740         Document document = kualiDocumentFormBase.getDocument();
741         // check authorization for reloading document
742         //DocumentActionFlags flags = getDocumentActionFlags(document);
743         if (!kualiDocumentFormBase.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_PERFORM_ROUTE_REPORT)) {
744             throw buildAuthorizationException("perform route report", document);
745         }
746 
747         String backUrlBase = getReturnLocation(request, mapping);
748         String globalVariableFormKey = GlobalVariables.getUserSession().addObjectWithGeneratedKey(form);
749         // setup back form variables
750         request.setAttribute("backUrlBase", backUrlBase);
751         List<KeyValue> backFormParameters = new ArrayList<KeyValue>();
752         backFormParameters.add(new ConcreteKeyValue(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL));
753         backFormParameters.add(new ConcreteKeyValue(KRADConstants.DOC_FORM_KEY, globalVariableFormKey));
754         request.setAttribute("backFormHiddenVariables", backFormParameters);
755 
756         // setup route report form variables
757         request.setAttribute("workflowRouteReportUrl", getKualiConfigurationService().getPropertyValueAsString(
758                 KRADConstants.WORKFLOW_URL_KEY) + "/" + KewApiConstants.DOCUMENT_ROUTING_REPORT_PAGE);
759         List<KeyValue> generalRouteReportFormParameters = new ArrayList<KeyValue>();
760         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.INITIATOR_ID_ATTRIBUTE_NAME, document.getDocumentHeader().getWorkflowDocument().getDocument().getInitiatorPrincipalId()));
761         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.DOCUMENT_TYPE_NAME_ATTRIBUTE_NAME, document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()));
762         // prepareForRouteReport() method should populate document header workflow document application content xml
763         String xml = document.getXmlForRouteReport();
764         if (LOG.isDebugEnabled()) {
765             LOG.debug("XML being used for Routing Report is: " + xml);
766         }
767         generalRouteReportFormParameters.add(new ConcreteKeyValue(KewApiConstants.DOCUMENT_CONTENT_ATTRIBUTE_NAME, xml));
768 
769         // set up the variables for the form if java script is working (includes a close button variable and no back url)
770         List<KeyValue> javaScriptFormParameters = new ArrayList<KeyValue>();
771         javaScriptFormParameters.addAll(generalRouteReportFormParameters);
772         javaScriptFormParameters.add(new ConcreteKeyValue(KewApiConstants.DISPLAY_CLOSE_BUTTON_ATTRIBUTE_NAME, KewApiConstants.DISPLAY_CLOSE_BUTTON_TRUE_VALUE));
773         request.setAttribute("javaScriptFormVariables", javaScriptFormParameters);
774 
775         // set up the variables for the form if java script is NOT working (includes a back url but no close button)
776         List<KeyValue> noJavaScriptFormParameters = new ArrayList<KeyValue>();
777         noJavaScriptFormParameters.addAll(generalRouteReportFormParameters);
778         Properties parameters = new Properties();
779         for (KeyValue pair : backFormParameters) {
780             parameters.put(pair.getKey(), pair.getValue());
781         }
782         noJavaScriptFormParameters.add(new ConcreteKeyValue(KewApiConstants.RETURN_URL_ATTRIBUTE_NAME, UrlFactory.parameterizeUrl(backUrlBase, parameters)));
783         request.setAttribute("noJavaScriptFormVariables", noJavaScriptFormParameters);
784 
785         return mapping.findForward(KRADConstants.MAPPING_ROUTE_REPORT);
786     }
787 
788     /**
789      * route the document using the document service
790      *
791      * @param mapping
792      * @param form
793      * @param request
794      * @param response
795      * @return ActionForward
796      * @throws Exception
797      */
798     public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
799         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
800         doProcessingAfterPost(kualiDocumentFormBase, request);
801 
802         kualiDocumentFormBase.setDerivedValuesOnForm(request);
803         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
804         if (preRulesForward != null) {
805             return preRulesForward;
806         }
807 
808         Document document = kualiDocumentFormBase.getDocument();
809 
810         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "route", "");
811         if (forward != null) {
812             return forward;
813         }
814 
815         getDocumentService().routeDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
816         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL);
817         kualiDocumentFormBase.setAnnotation("");
818 
819 //        GlobalVariables.getUserSession().addObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY,Boolean.TRUE);
820         return mapping.findForward(RiceConstants.MAPPING_BASIC);
821     }
822 
823     /**
824      * Calls the document service to blanket approve the document
825      *
826      * @param mapping
827      * @param form
828      * @param request
829      * @param response
830      * @return ActionForward
831      * @throws Exception
832      */
833     public ActionForward blanketApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
834         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
835         doProcessingAfterPost(kualiDocumentFormBase, request);
836 
837         // KULRICE-7864: blanket approve should not be allowed when adhoc route for completion request is newly added 
838         boolean hasPendingAdhocForCompletion = this.hasPendingAdhocForCompletion(kualiDocumentFormBase);
839         if(hasPendingAdhocForCompletion){
840             GlobalVariables.getMessageMap().putError(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME, RiceKeyConstants.ERROR_ADHOC_COMPLETE_BLANKET_APPROVE_NOT_ALLOWED);
841             
842             return mapping.findForward(RiceConstants.MAPPING_BASIC);
843         }
844         
845         kualiDocumentFormBase.setDerivedValuesOnForm(request);
846         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
847         if (preRulesForward != null) {
848             return preRulesForward;
849         }
850 
851         Document document = kualiDocumentFormBase.getDocument();
852 
853         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "blanketApprove", "");
854         if (forward != null) {
855             return forward;
856         }
857 
858         getDocumentService().blanketApproveDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
859         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_APPROVED);
860         kualiDocumentFormBase.setAnnotation("");
861         return returnToSender(request, mapping, kualiDocumentFormBase);
862     }
863 
864     /**
865      * Calls the document service to approve the document
866      *
867      * @param mapping
868      * @param form
869      * @param request
870      * @param response
871      * @return ActionForward
872      * @throws Exception
873      */
874     public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
875         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
876         doProcessingAfterPost(kualiDocumentFormBase, request);
877 
878         kualiDocumentFormBase.setDerivedValuesOnForm(request);
879         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
880         if (preRulesForward != null) {
881             return preRulesForward;
882         }
883 
884         Document document = kualiDocumentFormBase.getDocument();
885 
886         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "approve", "");
887         if (forward != null) {
888             return forward;
889         }
890 
891         getDocumentService().approveDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
892         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_APPROVED);
893         kualiDocumentFormBase.setAnnotation("");
894         return returnToSender(request, mapping, kualiDocumentFormBase);
895     }
896 
897     /**
898      * Calls the document service to disapprove the document
899      *
900      * @param mapping
901      * @param form
902      * @param request
903      * @param response
904      * @return ActionForward
905      * @throws Exception
906      */
907     public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request,
908                                     HttpServletResponse response) throws Exception {
909 
910         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);
911         ReasonPrompt.Response resp = prompt.ask(mapping, form, request, response);
912 
913         if (resp.forward != null) {
914             return resp.forward;
915         }
916 
917         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
918         doProcessingAfterPost(kualiDocumentFormBase, request);
919         getDocumentService().disapproveDocument(kualiDocumentFormBase.getDocument(), resp.reason);
920         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_DISAPPROVED);
921         kualiDocumentFormBase.setAnnotation("");
922 
923         return returnToSender(request, mapping, kualiDocumentFormBase);
924     }
925 
926     /**
927      * Calls the document service to cancel the document
928      *
929      * @param mapping
930      * @param form
931      * @param request
932      * @param response
933      * @return ActionForward
934      * @throws Exception
935      */
936     public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
937         Object question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
938         // this should probably be moved into a private instance variable
939         // logic for cancel question
940         if (question == null) {
941             // ask question if not already asked
942             return this.performQuestionWithoutInput(mapping, form, request, response, KRADConstants.DOCUMENT_CANCEL_QUESTION, getKualiConfigurationService().getPropertyValueAsString(
943                     "document.question.cancel.text"), KRADConstants.CONFIRMATION_QUESTION, KRADConstants.MAPPING_CANCEL, "");
944         } else {
945             Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
946             if ((KRADConstants.DOCUMENT_CANCEL_QUESTION.equals(question)) && ConfirmationQuestion.NO.equals(buttonClicked)) {
947                 // if no button clicked just reload the doc
948                 return mapping.findForward(RiceConstants.MAPPING_BASIC);
949             }
950             // else go to cancel logic below
951         }
952 
953         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
954         doProcessingAfterPost(kualiDocumentFormBase, request);
955         // KULRICE-4447 Call cancelDocument() only if the document exists
956         if (getDocumentService().documentExists(kualiDocumentFormBase.getDocId())) {
957             getDocumentService().cancelDocument(kualiDocumentFormBase.getDocument(), kualiDocumentFormBase.getAnnotation());
958         }
959 
960         return returnToSender(request, mapping, kualiDocumentFormBase);
961     }
962 
963     /**
964      * Calls the document service to disapprove the document
965      *
966      * @param mapping
967      * @param form
968      * @param request
969      * @param response
970      * @return ActionForward
971      * @throws Exception
972      */
973     public ActionForward recall(ActionMapping mapping, ActionForm form, HttpServletRequest request,
974             HttpServletResponse response) throws Exception {
975         
976         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);
977         ReasonPrompt.Response resp = prompt.ask(mapping, form, request, response);
978 
979         if (resp.forward != null) {
980             return resp.forward;
981         }
982         
983         boolean cancel = !((KRADConstants.DOCUMENT_RECALL_QUESTION.equals(resp.question)) && RecallQuestion.RECALL_TO_ACTIONLIST.equals(resp.button));
984 
985         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
986         doProcessingAfterPost(kualiDocumentFormBase, request);
987         getDocumentService().recallDocument(kualiDocumentFormBase.getDocument(), resp.reason, cancel);
988 
989         // just return to doc view
990         return mapping.findForward(RiceConstants.MAPPING_BASIC);
991     }
992 
993     /**
994      * Close the document and take the user back to the index; only after asking the user if they want to save the document first.
995      * Only users who have the "canSave()" permission are given this option.
996      *
997      * @param mapping
998      * @param form
999      * @param request
1000      * @param response
1001      * @return ActionForward
1002      * @throws Exception
1003      */
1004     public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1005         KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
1006         doProcessingAfterPost(docForm, request);
1007         Document document = docForm.getDocument();
1008         // only want to prompt them to save if they already can save
1009         if (canSave(docForm)) {
1010 
1011             Object question = getQuestion(request);
1012             // logic for close question
1013             if (question == null) {
1014                 // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1015                 // Stash the unconverted values to populate errors if the user elects to save
1016                 saveUnconvertedValuesToSession(request, docForm);
1017 
1018                 // ask question if not already asked
1019                 return this.performQuestionWithoutInput(mapping, form, request, response, KRADConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION, getKualiConfigurationService().getPropertyValueAsString(
1020                         RiceKeyConstants.QUESTION_SAVE_BEFORE_CLOSE), KRADConstants.CONFIRMATION_QUESTION, KRADConstants.MAPPING_CLOSE, "");
1021             } else {
1022                 Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
1023 
1024                 // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1025                 // Side effecting in that it clears the session attribute that holds the unconverted values.
1026                 Map<String, Object> unconvertedValues = restoreUnconvertedValuesFromSession(request, docForm);
1027 
1028                 if ((KRADConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
1029                     // if yes button clicked - save the doc
1030 
1031                     // KULRICE-7306: Unconverted Values not carried through during a saveOnClose action.
1032                     // If there were values that couldn't be converted, we attempt to populate them so that the
1033                     // the appropriate errors get set on those fields
1034                     if (MapUtils.isNotEmpty(unconvertedValues)) for (Map.Entry<String, Object> entry : unconvertedValues.entrySet()) {
1035                         docForm.populateForProperty(entry.getKey(), entry.getValue(), unconvertedValues);
1036                     }
1037 
1038                     ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "save", "");
1039                     if (forward != null) {
1040                         return forward;
1041                     }
1042 
1043                     getDocumentService().saveDocument(docForm.getDocument());
1044                 }
1045                 // else go to close logic below
1046             }
1047         }
1048 
1049         return returnToSender(request, mapping, docForm);
1050     }
1051 
1052     // stash unconvertedValues in the session
1053     private void saveUnconvertedValuesToSession(HttpServletRequest request, KualiDocumentFormBase docForm) {
1054         if (MapUtils.isNotEmpty(docForm.getUnconvertedValues())) {
1055             request.getSession().setAttribute(getUnconvertedValuesSessionAttributeKey(docForm), new HashMap(docForm.getUnconvertedValues()));
1056         }
1057     }
1058 
1059     // SIDE EFFECTING: clears out unconverted values from the Session and restores them to the form
1060     private Map<String, Object> restoreUnconvertedValuesFromSession(HttpServletRequest request,
1061             KualiDocumentFormBase docForm) {// first restore unconvertedValues and clear out of session
1062         Map<String, Object> unconvertedValues =
1063                 (Map<String, Object>)request.getSession().getAttribute(getUnconvertedValuesSessionAttributeKey(docForm));
1064         if (MapUtils.isNotEmpty(unconvertedValues)) {
1065             request.getSession().removeAttribute(getUnconvertedValuesSessionAttributeKey(docForm));
1066             docForm.setUnconvertedValues(unconvertedValues); // setting them here just for good measure
1067         }
1068         return unconvertedValues;
1069     }
1070 
1071     // create the key based on docId for stashing/retrieving unconvertedValues in the session
1072     private String getUnconvertedValuesSessionAttributeKey(KualiDocumentFormBase docForm) {
1073         return "preCloseUnconvertedValues." + docForm.getDocId();
1074     }
1075 
1076     protected boolean canSave(ActionForm form) {
1077         KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
1078         return docForm.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_CAN_SAVE);
1079     }
1080 
1081     protected Object getQuestion(HttpServletRequest request) {
1082         return request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
1083     }
1084 
1085     /**
1086      * call the document service to clear the fyis
1087      *
1088      * @param mapping
1089      * @param form
1090      * @param request
1091      * @param response
1092      * @return ActionForward
1093      * @throws Exception
1094      */
1095     public ActionForward fyi(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1096         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1097         doProcessingAfterPost(kualiDocumentFormBase, request);
1098         getDocumentService().clearDocumentFyi(kualiDocumentFormBase.getDocument(), combineAdHocRecipients(kualiDocumentFormBase));
1099         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_FYIED);
1100         kualiDocumentFormBase.setAnnotation("");
1101         return returnToSender(request, mapping, kualiDocumentFormBase);
1102     }
1103 
1104     /**
1105      * call the document service to acknowledge
1106      *
1107      * @param mapping
1108      * @param form
1109      * @param request
1110      * @param response
1111      * @return ActionForward
1112      * @throws Exception
1113      */
1114     public ActionForward acknowledge(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1115         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1116         doProcessingAfterPost(kualiDocumentFormBase, request);
1117         getDocumentService().acknowledgeDocument(kualiDocumentFormBase.getDocument(), kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
1118         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_ACKNOWLEDGED);
1119         kualiDocumentFormBase.setAnnotation("");
1120         return returnToSender(request, mapping, kualiDocumentFormBase);
1121     }
1122 
1123     /**
1124      * redirect to the supervisor functions that exist.
1125      *
1126      * @param mapping
1127      * @param form
1128      * @param request
1129      * @param response
1130      * @return ActionForward
1131      * @throws Exception
1132      */
1133     public ActionForward supervisorFunctions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1134         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1135 
1136 
1137         String workflowSuperUserUrl = getKualiConfigurationService().getPropertyValueAsString(
1138                 KRADConstants.WORKFLOW_URL_KEY) + "/SuperUser.do?methodToCall=displaySuperUserDocument&documentId=" + kualiDocumentFormBase.getDocument().getDocumentHeader().getDocumentNumber();
1139         response.sendRedirect(workflowSuperUserUrl);
1140 
1141         return null;
1142     }
1143 
1144     /**
1145      * Convenience method to combine the two lists of ad hoc recipients into one which should be done before calling any of the
1146      * document service methods that expect a list of ad hoc recipients
1147      *
1148      * @param kualiDocumentFormBase
1149      * @return List
1150      */
1151     protected List<AdHocRouteRecipient> combineAdHocRecipients(KualiDocumentFormBase kualiDocumentFormBase) {
1152         List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>();
1153         adHocRecipients.addAll(kualiDocumentFormBase.getAdHocRoutePersons());
1154         adHocRecipients.addAll(kualiDocumentFormBase.getAdHocRouteWorkgroups());
1155         return adHocRecipients;
1156     }
1157 
1158     /**
1159      * if the action desires to retain error messages generated by the rules framework for save/submit/etc. validation after returning from a lookup.
1160      *
1161      * @see KualiAction#refresh(org.apache.struts.action.ActionMapping,
1162      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1163      */
1164     @Override
1165     public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1166         KualiDocumentFormBase kualiForm = (KualiDocumentFormBase) form;
1167         kualiForm.setDerivedValuesOnForm(request);
1168 
1169         super.refresh(mapping, form, request, response);
1170         refreshAdHocRoutingWorkgroupLookups(request, kualiForm);
1171 
1172         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1173     }
1174 
1175     /**
1176      * special refresh needed to get the workgroups populated correctly when coming back from workgroup lookups
1177      *
1178      * @param request
1179      * @param kualiForm
1180      * @throws WorkflowException
1181      */
1182     @SuppressWarnings("unchecked")
1183     protected void refreshAdHocRoutingWorkgroupLookups(HttpServletRequest request, KualiDocumentFormBase kualiForm) throws WorkflowException {
1184         for (Enumeration<String> i = request.getParameterNames(); i.hasMoreElements();) {
1185             String parameterName = i.nextElement();
1186             if (parameterName.equals("newAdHocRouteWorkgroup.recipientName") && !"".equals(request.getParameter(parameterName))) {
1187                 //check for namespace
1188                 String namespace = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1189                 if (request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode") != null && !"".equals(request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode").trim())) {
1190                     namespace = request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode").trim();
1191                 }
1192                 Group group = getGroupService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(
1193                         parameterName));
1194                 if (group != null) {
1195                     kualiForm.getNewAdHocRouteWorkgroup().setId(group.getId());
1196                     kualiForm.getNewAdHocRouteWorkgroup().setRecipientName(group.getName());
1197                     kualiForm.getNewAdHocRouteWorkgroup().setRecipientNamespaceCode(group.getNamespaceCode());
1198                 } else {
1199                     GlobalVariables.getMessageMap().putError("newAdHocRouteWorkgroup.recipientNamespaceCode", RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_NAMESPACECODE);
1200                     return;
1201                 }
1202             }
1203             if (parameterName.startsWith("adHocRouteWorkgroup[") && !"".equals(request.getParameter(parameterName))) {
1204                 if (parameterName.endsWith(".recipientName")) {
1205                     int lineNumber = Integer.parseInt(StringUtils.substringBetween(parameterName, "[", "]"));
1206                     //check for namespace
1207                     String namespaceParam = "adHocRouteWorkgroup[" + lineNumber + "].recipientNamespaceCode";
1208                     String namespace = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1209                     if (request.getParameter(namespaceParam) != null && !"".equals(request.getParameter(namespaceParam).trim())) {
1210                         namespace = request.getParameter(namespaceParam).trim();
1211                     }
1212                     Group group = getGroupService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(
1213                             parameterName));
1214                     if (group != null) {
1215                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setId(group.getId());
1216                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientName(group.getName());
1217                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientNamespaceCode(group.getNamespaceCode());
1218                     } else {
1219                         GlobalVariables.getMessageMap().putError(namespaceParam, RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_NAMESPACECODE);
1220                         return;
1221                     }
1222                 }
1223             }
1224             /*
1225             if (parameterName.startsWith("newAdHocRouteWorkgroup[") && !"".equals(request.getParameter(parameterName))) {
1226                 if (parameterName.endsWith(".recipientName")) {
1227                     int lineNumber = Integer.parseInt(StringUtils.substringBetween(parameterName, "[", "]"));
1228                   //check for namespace
1229                     String namespaceParam = "newAdHocRouteWorkgroup[" + lineNumber + "].recipientNamespaceCode";
1230                     String namespace = KimApiConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE;
1231                     if (request.getParameter(namespaceParam) != null && !"".equals(request.getParameter(namespaceParam).trim())) {
1232                         namespace = request.getParameter(namespaceParam).trim();
1233                     }
1234                     KimGroup group = getIdentityManagementService().getGroupByNamespaceCodeAndName(namespace, request.getParameter(parameterName));
1235                     if (group != null) {
1236                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setId(group.getGroupId());
1237                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientName(group.getGroupName());
1238                         kualiForm.getAdHocRouteWorkgroup(lineNumber).setRecipientNamespaceCode(group.getNamespaceCode());
1239                     } else {
1240                         throw new RuntimeException("Invalid workgroup id passed as parameter.");
1241                     }
1242                 }
1243             }
1244             */
1245         }
1246     }
1247 
1248 
1249     /**
1250      * Cancels the pending attachment, if any.
1251      *
1252      * @param mapping
1253      * @param form
1254      * @param request
1255      * @param response
1256      * @return ActionForward
1257      * @throws Exception
1258      */
1259     public ActionForward cancelBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1260         KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
1261 
1262         // blank current attachmentFile
1263         documentForm.setAttachmentFile(new BlankFormFile());
1264 
1265         // remove current attachment, if any
1266         Note note = documentForm.getNewNote();
1267         note.removeAttachment();
1268         documentForm.setNewNote(note);
1269 
1270         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1271     }
1272 
1273     /**
1274      * Handy method to stream the byte array to response object
1275      *
1276      * @param fileContents
1277      * @param fileName
1278      * @param fileContentType
1279      * @param response
1280      * @throws Exception
1281      */
1282     protected void streamToResponse(byte[] fileContents, String fileName, String fileContentType, HttpServletResponse response) throws Exception {
1283         ByteArrayOutputStream baos = null;
1284         try {
1285             baos = new ByteArrayOutputStream(fileContents.length);
1286             baos.write(fileContents);
1287             WebUtils.saveMimeOutputStreamAsFile(response, fileContentType, baos, fileName);
1288         } finally {
1289             try {
1290                 if (baos != null) {
1291                     baos.close();
1292                     baos = null;
1293                 }
1294             } catch (IOException ioEx) {
1295                 LOG.error("Error while downloading attachment");
1296                 throw new RuntimeException("IOException occurred while downloading attachment", ioEx);
1297             }
1298         }
1299     }
1300 
1301     /**
1302      * Downloads the selected attachment to the user's browser
1303      *
1304      * @param mapping
1305      * @param form
1306      * @param request
1307      * @param response
1308      * @return ActionForward
1309      * @throws Exception
1310      */
1311     public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1312         KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
1313 
1314         int attachmentIndex = selectedAttachmentIndex(request);
1315         if (attachmentIndex >= 0) {
1316             Note note = documentForm.getDocument().getNote(attachmentIndex);
1317             Attachment attachment = note.getAttachment();
1318             //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)
1319             attachment.setNote(note);
1320 
1321             // since we're downloading a file, all of the editable properties from the previous request will continue to be editable.
1322             documentForm.copyPopulateEditablePropertiesToActionEditableProperties();
1323 
1324             WebUtils.saveMimeInputStreamAsFile(response, attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentContents(), attachment.getAttachmentFileName(), attachment.getAttachmentFileSize().intValue());
1325             return null;
1326         }
1327 
1328         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1329     }
1330 
1331 
1332     /**
1333      * @param request
1334      * @return index of the attachment whose download button was just pressed
1335      */
1336     protected int selectedAttachmentIndex(HttpServletRequest request) {
1337         int attachmentIndex = -1;
1338 
1339         String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
1340         if (StringUtils.isNotBlank(parameterName)) {
1341             String attachmentIndexParam = StringUtils.substringBetween(parameterName, ".attachment[", "].");
1342 
1343             try {
1344                 attachmentIndex = Integer.parseInt(attachmentIndexParam);
1345             } catch (NumberFormatException ignored) {
1346             }
1347         }
1348 
1349         return attachmentIndex;
1350     }
1351 
1352 
1353     /**
1354      * insert a note into the document
1355      *
1356      * @param mapping
1357      * @param form
1358      * @param request
1359      * @param response
1360      * @return ActionForward
1361      * @throws Exception
1362      */
1363     public ActionForward insertBONote(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1364         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1365         Document document = kualiDocumentFormBase.getDocument();
1366         Note newNote = kualiDocumentFormBase.getNewNote();
1367         newNote.setNotePostedTimestampToCurrent();
1368 
1369         String attachmentTypeCode = null;
1370 
1371         FormFile attachmentFile = kualiDocumentFormBase.getAttachmentFile();
1372         if (attachmentFile == null) {
1373             GlobalVariables.getMessageMap().putError(
1374                     String.format("%s.%s",
1375                             KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1376                             KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME),
1377                     RiceKeyConstants.ERROR_UPLOADFILE_NULL);
1378             // This line was removed in order to continue to validates other
1379             // return mapping.findForward(RiceConstants.MAPPING_BASIC);
1380         }
1381 
1382         if (newNote.getAttachment() != null) {
1383             attachmentTypeCode = newNote.getAttachment().getAttachmentTypeCode();
1384         }
1385 
1386         // check authorization for adding notes
1387         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1388         if (!documentAuthorizer.canAddNoteAttachment(document, attachmentTypeCode, GlobalVariables.getUserSession().getPerson())) {
1389             throw buildAuthorizationException("annotate", document);
1390         }
1391 
1392         // create the attachment first, so that failure-to-create-attachment can be treated as a validation failure
1393 
1394         Attachment attachment = null;
1395         if (attachmentFile != null && !StringUtils.isBlank(attachmentFile.getFileName())) {
1396             if (attachmentFile.getFileSize() == 0) {
1397                 GlobalVariables.getMessageMap().putError(
1398                         String.format("%s.%s",
1399                                 KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1400                                 KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME),
1401                         RiceKeyConstants.ERROR_UPLOADFILE_EMPTY,
1402                         attachmentFile.getFileName());
1403                 // This line was removed in order to continue to validates other
1404 //                return mapping.findForward(RiceConstants.MAPPING_BASIC);
1405             } else {
1406                 String attachmentType = null;
1407                 Attachment newAttachment = kualiDocumentFormBase.getNewNote().getAttachment();
1408                 if (newAttachment != null) {
1409                     attachmentType = newAttachment.getAttachmentTypeCode();
1410                 }
1411                 attachment = getAttachmentService().createAttachment(document.getNoteTarget(), attachmentFile.getFileName(), attachmentFile.getContentType(), attachmentFile.getFileSize(), attachmentFile.getInputStream(), attachmentType);
1412             }
1413         }
1414 
1415         DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
1416         org.kuali.rice.krad.datadictionary.DocumentEntry entry = dataDictionary.getDocumentEntry(document.getClass().getName());
1417 
1418         if (entry.getDisplayTopicFieldInNotes()) {
1419             String topicText = kualiDocumentFormBase.getNewNote().getNoteTopicText();
1420             if (StringUtils.isBlank(topicText)) {
1421                 GlobalVariables.getMessageMap().putError(
1422                         String.format("%s.%s",
1423                                 KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
1424                                 KRADConstants.NOTE_TOPIC_TEXT_PROPERTY_NAME),
1425                         RiceKeyConstants.ERROR_REQUIRED,
1426                         "Note Topic (Note Topic)");
1427             }
1428         }
1429 
1430         // create a new note from the data passed in
1431         // TODO gah! this is awful
1432         Person kualiUser = GlobalVariables.getUserSession().getPerson();
1433         if (kualiUser == null) {
1434             throw new IllegalStateException("Current UserSession has a null Person.");
1435         }
1436         Note tmpNote = getNoteService().createNote(newNote, document.getNoteTarget(), kualiUser.getPrincipalId());
1437 
1438         ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response, KRADPropertyConstants.NOTE, tmpNote.getNoteText(), "insertBONote", "");
1439         if (forward != null) {
1440             return forward;
1441         }
1442 
1443         // validate the note
1444         boolean rulePassed = getKualiRuleService().applyRules(new AddNoteEvent(document, tmpNote));
1445 
1446         // if the rule evaluation passed, let's add the note
1447         if (rulePassed) {
1448             tmpNote.refresh();
1449 
1450 
1451             DocumentHeader documentHeader = document.getDocumentHeader();
1452 
1453             // associate note with object now
1454             document.addNote(tmpNote);
1455 
1456             // persist the note if the document is already saved the getObjectId check is to get around a bug with certain documents where
1457             // "saved" doesn't really persist, if you notice any problems with missing notes check this line
1458             //maintenance document BO note should only be saved into table when document is in the PROCESSED workflow status
1459             if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty(document.getNoteTarget().getObjectId())
1460                     && !(document instanceof MaintenanceDocument && NoteType.BUSINESS_OBJECT.getCode().equals(tmpNote.getNoteTypeCode()))
1461                     ) {
1462                 getNoteService().save(tmpNote);
1463             }
1464             // adding the attachment after refresh gets called, since the attachment record doesn't get persisted
1465             // until the note does (and therefore refresh doesn't have any attachment to autoload based on the id, nor does it
1466             // autopopulate the id since the note hasn't been persisted yet)
1467             if (attachment != null) {
1468                 tmpNote.addAttachment(attachment);
1469                 // save again for attachment, note this is because sometimes the attachment is added first to the above then ojb tries to save
1470                 //without the PK on the attachment I think it is safer then trying to get the sequence manually
1471                 if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty(document.getNoteTarget().getObjectId())
1472                         && !(document instanceof MaintenanceDocument && NoteType.BUSINESS_OBJECT.getCode().equals(tmpNote.getNoteTypeCode()))
1473                         ) {
1474                     getNoteService().save(tmpNote);
1475                 }
1476             }
1477 
1478 
1479             // reset the new note back to an empty one
1480             kualiDocumentFormBase.setNewNote(new Note());
1481         }
1482 
1483 
1484         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1485     }
1486 
1487     /**
1488      * delete a note from the document
1489      *
1490      * @param mapping
1491      * @param form
1492      * @param request
1493      * @param response
1494      * @return ActionForward
1495      * @throws Exception
1496      */
1497     public ActionForward deleteBONote(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1498         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1499         Document document = kualiDocumentFormBase.getDocument();
1500 
1501 
1502 //        DataDictionary dataDictionary = getDataDictionaryService().getDataDictionary();
1503 //        DocumentEntry entry = dataDictionary.getDocumentEntry(document.getClass().getName());
1504 
1505         // check authorization for adding notes
1506         //DocumentActionFlags flags = getDocumentActionFlags(document);
1507         //if (!kualiDocumentFormBase.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_CAN_ANNOTATE)) {
1508         //    buildAuthorizationException("annotate", document);
1509         //    return mapping.findForward(RiceConstants.MAPPING_BASIC);
1510         //}
1511 
1512         // ok to delete the note/attachment
1513         // derive the note property from the newNote on the form
1514         Note newNote = kualiDocumentFormBase.getNewNote();
1515         Note note = document.getNote(getLineToDelete(request));
1516         Attachment attachment = note.getAttachment();
1517         String attachmentTypeCode = null;
1518         if (attachment != null) {
1519             attachmentTypeCode = attachment.getAttachmentTypeCode();
1520         }
1521         String authorUniversalIdentifier = note.getAuthorUniversalIdentifier();
1522         if (!WebUtils.canDeleteNoteAttachment(document, attachmentTypeCode, authorUniversalIdentifier)) {
1523             throw buildAuthorizationException("annotate", document);
1524         }
1525 
1526         if (attachment != null) { // only do this if the note has been persisted
1527             //KFSMI-798 - refresh() changed to refreshNonUpdateableReferences()
1528             //All references for the business object Attachment are auto-update="none",
1529             //so refreshNonUpdateableReferences() should work the same as refresh()
1530             if (note.getNoteIdentifier() != null) { // KULRICE-2343 don't blow away note reference if the note wasn't persisted
1531                 attachment.refreshNonUpdateableReferences();
1532             }
1533             getAttachmentService().deleteAttachmentContents(attachment);
1534         }
1535         // delete the note if the document is already saved
1536         if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
1537             getNoteService().deleteNote(note);
1538         }
1539         document.removeNote(note);
1540 
1541         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1542     }
1543 
1544     /**
1545      * Override this to customize which routing action to take when sending a note.  This method reads the system parameter
1546      * KR-NS/Document/SEND_NOTE_WORKFLOW_NOTIFICATION_ACTIONS to determine which action to take
1547      *
1548      * @param request
1549      * @param note
1550      * @return a value from {@link KewApiConstants}
1551      */
1552     protected String determineNoteWorkflowNotificationAction(HttpServletRequest request, KualiDocumentFormBase kualiDocumentFormBase, Note note) {
1553         return getParameterService().getParameterValueAsString(KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_DETAIL_TYPE, KRADConstants.SEND_NOTE_WORKFLOW_NOTIFICATION_ACTIONS_PARM_NM);
1554     }
1555 
1556     public ActionForward sendNoteWorkflowNotification(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1557         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1558         Document document = kualiDocumentFormBase.getDocument();
1559 
1560         Note note = document.getNote(getSelectedLine(request));
1561 
1562         // verify recipient was specified
1563         if (StringUtils.isBlank(note.getAdHocRouteRecipient().getId())) {
1564             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.NEW_DOCUMENT_NOTE, RiceKeyConstants.ERROR_SEND_NOTE_NOTIFICATION_RECIPIENT);
1565             return mapping.findForward(RiceConstants.MAPPING_BASIC);
1566         }
1567         // check recipient is valid
1568         else {
1569             note.getAdHocRouteRecipient().setActionRequested(determineNoteWorkflowNotificationAction(request, kualiDocumentFormBase, note));
1570 
1571             boolean rulePassed = getKualiRuleService().applyRules(new AddAdHocRoutePersonEvent(KRADPropertyConstants.NEW_DOCUMENT_NOTE, document, (AdHocRoutePerson) note.getAdHocRouteRecipient()));
1572             if (!rulePassed) {
1573                 return mapping.findForward(RiceConstants.MAPPING_BASIC);
1574             }
1575         }
1576 
1577         // if document is saved, send notification
1578         if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
1579             getDocumentService().sendNoteRouteNotification(document, note, GlobalVariables.getUserSession().getPerson());
1580 
1581             // add success message
1582             KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SEND_NOTE_NOTIFICATION_SUCCESSFUL);
1583         } else {
1584             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.NEW_DOCUMENT_NOTE, RiceKeyConstants.ERROR_SEND_NOTE_NOTIFICATION_DOCSTATUS);
1585         }
1586 
1587         return mapping.findForward(RiceConstants.MAPPING_BASIC);
1588     }
1589 
1590 
1591     /**
1592      * Generates detailed log messages for OptimisticLockExceptions
1593      *
1594      * @param e
1595      */
1596     private final void logOjbOptimisticLockException(OptimisticLockException e) {
1597         if (LOG.isInfoEnabled()) {
1598             StringBuffer message = new StringBuffer("caught OptimisticLockException, caused by ");
1599             Object sourceObject = e.getSourceObject();
1600             String infix = null;
1601             try {
1602                 // try to add instance details
1603                 infix = sourceObject.toString();
1604             } catch (Exception e2) {
1605                 // just use the class name
1606                 infix = sourceObject.getClass().getName();
1607             }
1608             message.append(infix);
1609 
1610             if (sourceObject instanceof PersistableBusinessObject) {
1611                 PersistableBusinessObject persistableObject = (PersistableBusinessObject) sourceObject;
1612                 message.append(" [versionNumber = ").append(persistableObject.getVersionNumber()).append("]");
1613             }
1614 
1615             LOG.info(message.toString(), e);
1616         }
1617     }
1618 
1619 
1620     /**
1621      * Makes calls to the PromptBeforeValidation specified for the document. If the class returns an actionforward, that forward
1622      * will be returned (thus controlling how execution occurs), or null.
1623      *
1624      * @param mapping
1625      * @param form
1626      * @param request
1627      * @param response
1628      * @return
1629      * @throws Exception
1630      */
1631     public ActionForward promptBeforeValidation(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1632         return promptBeforeValidation(mapping, form, request, response, "route");
1633     }
1634 
1635     /**
1636      * Makes calls to the PromptBeforeValidation specified for the document. If the class returns an actionforward, that forward
1637      * will be returned (thus controlling how execution occurs), or null.
1638      *
1639      * @param mapping
1640      * @param form
1641      * @param request
1642      * @param response
1643      * @param methodToCall
1644      * @return
1645      * @throws Exception
1646      */
1647     public ActionForward promptBeforeValidation(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String methodToCall) throws Exception {
1648         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
1649 
1650         /* callback to any pre rules check class */
1651         Class<? extends PromptBeforeValidation> promptBeforeValidationClass = getDataDictionaryService().getPromptBeforeValidationClass(kualiDocumentFormBase.getDocTypeName());
1652         if (LOG.isDebugEnabled()) {
1653             LOG.debug("PromptBeforeValidationClass: " + promptBeforeValidationClass);
1654         }
1655         if (promptBeforeValidationClass != null) {
1656             PromptBeforeValidation promptBeforeValidation = promptBeforeValidationClass.newInstance();
1657             PromptBeforeValidationEvent event = new PromptBeforeValidationEvent("Pre Maint route Check", "", kualiDocumentFormBase.getDocument());
1658             boolean continueRoute = promptBeforeValidation.processPrompts(form, request, event);
1659             if (!continueRoute) {
1660                 if (event.isPerformQuestion()) {
1661                     return super.performQuestionWithoutInput(mapping, kualiDocumentFormBase, request, response, event.getQuestionId(), event.getQuestionText(), event.getQuestionType(), methodToCall, event.getQuestionContext());
1662                 } else {
1663                     // This error section is here to avoid a silent and very confusing failure. If the PreRule
1664                     // instance returns a null for the processPreRuleChecks above, but does not set an
1665                     // ActionForwardName on the event, processing will just silently fail here, and the user
1666                     // will be presented with a blank frame.
1667                     //
1668                     // If the processPreRuleCheck() returns a false, an ActionForwardName needs to be set before hand
1669                     // by the PreRule class.
1670                     ActionForward actionForward = mapping.findForward(event.getActionForwardName());
1671                     if (actionForward == null) {
1672                         throw new RuntimeException("No ActionForwardName defined on this Event, no further actions will be processed.");
1673                     }
1674                     return actionForward;
1675                 }
1676             }
1677         }
1678 
1679         return null;
1680     }
1681 
1682 
1683     /**
1684      * Convenience method for building authorization exceptions
1685      *
1686      * @param action
1687      * @param document
1688      */
1689     protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) {
1690         return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), action, document.getDocumentNumber());
1691     }
1692 
1693     protected boolean exitingDocument() {
1694     	String methodCalledViaDispatch = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY);
1695         String methodCompleted = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY);
1696         return StringUtils.isNotEmpty(methodCompleted) && StringUtils.isNotEmpty(methodCalledViaDispatch) && methodCompleted.startsWith(methodCalledViaDispatch);
1697     }
1698 
1699     protected void setupDocumentExit() {
1700     	String methodCalledViaDispatch = (String) GlobalVariables.getUserSession().retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY);
1701     	if(StringUtils.isNotEmpty(methodCalledViaDispatch)) {
1702     		GlobalVariables.getUserSession().addObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY, (Object) (methodCalledViaDispatch + DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_MARKER));
1703     	}
1704     }
1705 
1706     /**
1707      * If the given form has returnToActionList set to true, this method returns an ActionForward that should take the user back to
1708      * their action list; otherwise, it returns them to the portal.
1709      *
1710      * @param form
1711      * @return
1712      */
1713     protected ActionForward returnToSender(HttpServletRequest request, ActionMapping mapping, KualiDocumentFormBase form) {
1714         final ActionForward dest;
1715         if (form.isReturnToActionList()) {
1716             String workflowBase = getKualiConfigurationService().getPropertyValueAsString(
1717                     KRADConstants.WORKFLOW_URL_KEY);
1718             String actionListUrl = workflowBase + "/ActionList.do";
1719 
1720             dest = new ActionForward(actionListUrl, true);
1721         } else if (StringUtils.isNotBlank(form.getBackLocation())) {
1722             dest = new ActionForward(form.getBackLocation(), true);
1723         } else {
1724             dest = mapping.findForward(KRADConstants.MAPPING_PORTAL);
1725         }
1726 
1727         setupDocumentExit();
1728         return dest;
1729     }
1730 
1731     @SuppressWarnings("unchecked")
1732     protected void populateAuthorizationFields(KualiDocumentFormBase formBase) {
1733         if (formBase.isFormDocumentInitialized()) {
1734             Document document = formBase.getDocument();
1735             Person user = GlobalVariables.getUserSession().getPerson();
1736             DocumentPresentationController documentPresentationController = KNSServiceLocator
1737                     .getDocumentHelperService().getDocumentPresentationController(document);
1738             DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1739             Set<String> documentActions = documentPresentationController.getDocumentActions(document);
1740             documentActions = documentAuthorizer.getDocumentActions(document, user, documentActions);
1741 
1742             if (getDataDictionaryService().getDataDictionary().getDocumentEntry(document.getClass().getName()).getUsePessimisticLocking()) {
1743                 documentActions = getPessimisticLockService().getDocumentActions(document, user, documentActions);
1744             }
1745 
1746             //DocumentActionFlags flags = new DocumentActionFlags();
1747             formBase.setDocumentActions(convertSetToMap(documentActions));
1748 
1749         }
1750     }
1751 
1752     protected void populateAdHocActionRequestCodes(KualiDocumentFormBase formBase) {
1753         Document document = formBase.getDocument();
1754         DocumentAuthorizer documentAuthorizer = getDocumentHelperService().getDocumentAuthorizer(document);
1755         Map<String, String> adHocActionRequestCodes = new HashMap<String, String>();
1756 
1757         if (documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_FYI_REQ, GlobalVariables.getUserSession().getPerson())) {
1758             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ_LABEL);
1759         }
1760         if (!document.getDocumentHeader().getWorkflowDocument().isFinal() && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, GlobalVariables.getUserSession().getPerson())) {
1761             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ_LABEL);
1762         }
1763         if (!(document.getDocumentHeader().getWorkflowDocument().isApproved() || document.getDocumentHeader().getWorkflowDocument().isProcessed() || document.getDocumentHeader().getWorkflowDocument().isFinal()) && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, GlobalVariables.getUserSession().getPerson())) {
1764             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ_LABEL);
1765         }
1766 
1767         if ((document.getDocumentHeader().getWorkflowDocument().isInitiated() || document.getDocumentHeader().getWorkflowDocument().isSaved())
1768                 && documentAuthorizer.canSendAdHocRequests(document, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, GlobalVariables.getUserSession().getPerson())) {
1769             // Check if there is already a request for completion pending for the document.
1770             adHocActionRequestCodes.put(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ_LABEL);
1771         }
1772         formBase.setAdHocActionRequestCodes(adHocActionRequestCodes);
1773 
1774     }
1775 
1776 
1777     @SuppressWarnings("unchecked")
1778     protected Map convertSetToMap(Set s) {
1779         Map map = new HashMap();
1780         Iterator i = s.iterator();
1781         while (i.hasNext()) {
1782             Object key = i.next();
1783             map.put(key, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
1784         }
1785         return map;
1786     }
1787 
1788     /**
1789      * @return the dataDictionaryService
1790      */
1791     protected DataDictionaryService getDataDictionaryService() {
1792         if (dataDictionaryService == null) {
1793             dataDictionaryService = KNSServiceLocator.getDataDictionaryService();
1794         }
1795         return dataDictionaryService;
1796     }
1797 
1798     protected DocumentHelperService getDocumentHelperService() {
1799         if (documentHelperService == null) {
1800             documentHelperService = KNSServiceLocator.getDocumentHelperService();
1801         }
1802         return this.documentHelperService;
1803     }
1804 
1805     protected DocumentService getDocumentService() {
1806         if (documentService == null) {
1807             documentService = KRADServiceLocatorWeb.getDocumentService();
1808         }
1809         return this.documentService;
1810     }
1811 
1812     protected ConfigurationService getKualiConfigurationService() {
1813         if (kualiConfigurationService == null) {
1814             kualiConfigurationService = CoreApiServiceLocator.getKualiConfigurationService();
1815         }
1816         return this.kualiConfigurationService;
1817     }
1818 
1819     protected ParameterService getParameterService() {
1820         if (parameterService == null) {
1821             parameterService = CoreFrameworkServiceLocator.getParameterService();
1822         }
1823         return this.parameterService;
1824     }
1825 
1826     protected PessimisticLockService getPessimisticLockService() {
1827         if (pessimisticLockService == null) {
1828             pessimisticLockService = KRADServiceLocatorWeb.getPessimisticLockService();
1829         }
1830         return this.pessimisticLockService;
1831     }
1832 
1833     protected KualiRuleService getKualiRuleService() {
1834         if (kualiRuleService == null) {
1835             kualiRuleService = KRADServiceLocatorWeb.getKualiRuleService();
1836         }
1837         return this.kualiRuleService;
1838     }
1839 
1840     protected GroupService getGroupService() {
1841         if (groupService == null) {
1842             groupService = KimApiServiceLocator.getGroupService();
1843         }
1844         return this.groupService;
1845     }
1846 
1847     protected AttachmentService getAttachmentService() {
1848         if (attachmentService == null) {
1849             attachmentService = KRADServiceLocator.getAttachmentService();
1850         }
1851         return this.attachmentService;
1852     }
1853 
1854     protected NoteService getNoteService() {
1855         if (noteService == null) {
1856             noteService = KRADServiceLocator.getNoteService();
1857         }
1858         return this.noteService;
1859     }
1860 
1861     protected BusinessObjectService getBusinessObjectService() {
1862         if (businessObjectService == null) {
1863             businessObjectService = KRADServiceLocator.getBusinessObjectService();
1864         }
1865         return this.businessObjectService;
1866     }
1867 
1868     @Override
1869     protected BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
1870         if (businessObjectAuthorizationService == null) {
1871             businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
1872         }
1873         return businessObjectAuthorizationService;
1874     }
1875 
1876     public BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
1877         if (businessObjectMetaDataService == null) {
1878             businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
1879         }
1880         return this.businessObjectMetaDataService;
1881     }
1882 
1883     public EntityManagerFactory getEntityManagerFactory() {
1884         if (entityManagerFactory == null) {
1885             entityManagerFactory = KRADServiceLocator.getApplicationEntityManagerFactory();
1886         }
1887         return this.entityManagerFactory;
1888     }
1889 
1890     /**
1891      * @see KualiAction#hideAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1892      */
1893     @Override
1894     public ActionForward hideAllTabs(ActionMapping mapping, ActionForm form,
1895                                      HttpServletRequest request, HttpServletResponse response)
1896             throws Exception {
1897         if (form instanceof KualiDocumentFormBase) {
1898             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1899         }
1900         return super.hideAllTabs(mapping, form, request, response);
1901     }
1902 
1903     /**
1904      * @see KualiAction#showAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1905      */
1906     @Override
1907     public ActionForward showAllTabs(ActionMapping mapping, ActionForm form,
1908                                      HttpServletRequest request, HttpServletResponse response)
1909             throws Exception {
1910         if (form instanceof KualiDocumentFormBase) {
1911             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1912         }
1913         return super.showAllTabs(mapping, form, request, response);
1914     }
1915 
1916     /**
1917      * @see KualiAction#toggleTab(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1918      */
1919     @Override
1920     public ActionForward toggleTab(ActionMapping mapping, ActionForm form,
1921                                    HttpServletRequest request, HttpServletResponse response)
1922             throws Exception {
1923         if (form instanceof KualiDocumentFormBase) {
1924             WebUtils.reuseErrorMapFromPreviousRequest((KualiDocumentFormBase) form);
1925         }
1926         return super.toggleTab(mapping, form, request, response);
1927     }
1928 
1929     @Override
1930     protected void doProcessingAfterPost(KualiForm form, HttpServletRequest request) {
1931         super.doProcessingAfterPost(form, request);
1932         if (form instanceof KualiDocumentFormBase) {
1933             Document document = ((KualiDocumentFormBase) form).getDocument();
1934 
1935             getBusinessObjectService().linkUserFields(document);
1936         }
1937     }
1938 
1939     /**
1940      * Class that encapsulates the workflow for obtaining an reason from an action prompt.
1941      */
1942     private class ReasonPrompt {
1943         final String questionId;
1944         final String questionTextKey;
1945         final String questionType;
1946         final String missingReasonKey;
1947         final String questionCallerMapping;
1948         final String abortButton;
1949         final String noteIntroKey;
1950 
1951         private class Response {
1952             final String question;
1953             final ActionForward forward;
1954             final String reason;
1955             final String button;
1956             Response(String question, ActionForward forward) {
1957                 this(question, forward, null, null);
1958             }
1959             Response(String question, String reason, String button) {
1960                 this(question, null, reason, button);
1961             }
1962             private Response(String question, ActionForward forward, String reason, String button) {
1963                 this.question = question;
1964                 this.forward = forward;
1965                 this.reason = reason;
1966                 this.button = button;
1967             }
1968         }
1969 
1970         /**
1971          * @param questionId the question id/instance, 
1972          * @param questionTextKey application resources key for question text
1973          * @param questionType the {@link org.kuali.rice.kns.question.Question} question type
1974          * @param questionCallerMapping mapping of original action
1975          * @param abortButton button value considered to abort the prompt and return (optional, may be null)
1976          * @param noteIntroKey application resources key for quesiton text prefix (optional, may be null)
1977          */
1978         private ReasonPrompt(String questionId, String questionTextKey, String questionType, String missingReasonKey, String questionCallerMapping, String abortButton, String noteIntroKey) {
1979             this.questionId = questionId;
1980             this.questionTextKey = questionTextKey;
1981             this.questionType = questionType;
1982             this.questionCallerMapping = questionCallerMapping;
1983             this.abortButton = abortButton;
1984             this.noteIntroKey = noteIntroKey;
1985             this.missingReasonKey = missingReasonKey;
1986         }
1987 
1988         /**
1989          * Obtain a validated reason and button value via a Question prompt.  Reason is validated against
1990          * sensitive data patterns, and max Note text length
1991          * @param mapping Struts mapping
1992          * @param form Struts form
1993          * @param request http request
1994          * @param response http response
1995          * @return Response object representing *either*: 1) an ActionForward due to error or abort 2) a reason and button clicked
1996          * @throws Exception
1997          */
1998         public Response ask(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1999             String question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
2000             String reason = request.getParameter(KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME);
2001 
2002             if (StringUtils.isBlank(reason)) {
2003                 String context = request.getParameter(KRADConstants.QUESTION_CONTEXT);
2004                 if (context != null && StringUtils.contains(context, KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=")) {
2005                     reason = StringUtils.substringAfter(context, KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=");
2006                 }
2007             }
2008 
2009             String disapprovalNoteText = "";
2010 
2011             // start in logic for confirming the disapproval
2012             if (question == null) {
2013                 // ask question if not already asked
2014                 return new Response(question, performQuestionWithInput(mapping, form, request, response,
2015                         this.questionId,
2016                         getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2017                         this.questionType, this.questionCallerMapping, ""));
2018             }
2019 
2020             String buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
2021             if (this.questionId.equals(question) && abortButton != null && abortButton.equals(buttonClicked)) {
2022                 // if no button clicked just reload the doc
2023                 return new Response(question, mapping.findForward(RiceConstants.MAPPING_BASIC));
2024             }
2025 
2026             // have to check length on value entered
2027             String introNoteMessage = "";
2028             if (noteIntroKey != null) {
2029                 introNoteMessage = getKualiConfigurationService().getPropertyValueAsString(this.noteIntroKey) + KRADConstants.BLANK_SPACE;
2030             }
2031 
2032             // build out full message
2033             disapprovalNoteText = introNoteMessage + reason;
2034 
2035             // check for sensitive data in note
2036             boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
2037                     KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
2038                     KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
2039             if (warnForSensitiveData) {
2040                 String context = KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME + "=" + reason;
2041                 ActionForward forward = checkAndWarnAboutSensitiveData(mapping, form, request, response,
2042                         KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, disapprovalNoteText, this.questionCallerMapping, context);
2043                 if (forward != null) {
2044                     return new Response(question, forward);
2045                 }
2046             } else {
2047                 if (KRADUtils.containsSensitiveDataPatternMatch(disapprovalNoteText)) {
2048                     return new Response(question, performQuestionWithInputAgainBecauseOfErrors(mapping, form, request, response,
2049                             this.questionId, getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2050                             this.questionType, this.questionCallerMapping, "", reason,
2051                             RiceKeyConstants.ERROR_DOCUMENT_FIELD_CONTAINS_POSSIBLE_SENSITIVE_DATA,
2052                             KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, "reason"));
2053                 }
2054             }
2055 
2056             int disapprovalNoteTextLength = disapprovalNoteText.length();
2057 
2058             // get note text max length from DD
2059             int noteTextMaxLength = getDataDictionaryService().getAttributeMaxLength(Note.class, KRADConstants.NOTE_TEXT_PROPERTY_NAME);
2060 
2061             if (StringUtils.isBlank(reason) || (disapprovalNoteTextLength > noteTextMaxLength)) {
2062 
2063                 if (reason == null) {
2064                     // prevent a NPE by setting the reason to a blank string
2065                     reason = "";
2066                 }
2067                 return new Response(question, performQuestionWithInputAgainBecauseOfErrors(mapping, form, request, response,
2068                         this.questionId,
2069                         getKualiConfigurationService().getPropertyValueAsString(this.questionTextKey),
2070                         this.questionType, this.questionCallerMapping, "", reason,
2071                         this.missingReasonKey,
2072                         KRADConstants.QUESTION_REASON_ATTRIBUTE_NAME, Integer.toString(noteTextMaxLength)));
2073             }
2074 
2075             return new Response(question, disapprovalNoteText, buttonClicked);
2076         }
2077     }
2078 
2079     public ActionForward takeSuperUserActions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2080         KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2081         if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2082             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.annotation.missing", "");
2083             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2084         } else if(documentForm.getSelectedActionRequests().isEmpty()) {
2085             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.none.selected", "");
2086             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2087         }  else if (!documentForm.isStateAllowsApproveSingleActionRequest()) {
2088             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.takeactions.not.allowed", "");
2089             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2090         }
2091 
2092         for(String actionRequestId : documentForm.getSelectedActionRequests()) {
2093             ActionRequest actionRequest = null;
2094             for(ActionRequest pendingActionRequest : documentForm.getActionRequests()) {
2095                 if(StringUtils.equals(pendingActionRequest.getId(), actionRequestId)) {
2096                     actionRequest = pendingActionRequest;
2097                     break;
2098                 }
2099             }
2100             if(actionRequest == null) {
2101                 // If the action request isn't pending then skip it
2102                 continue;
2103             }
2104             if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.COMPLETE.getCode()) ||
2105                 StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.APPROVE.getCode())) {
2106                     getDocumentService().validateAndPersistDocument(documentForm.getDocument(), new RouteDocumentEvent(documentForm.getDocument()));
2107             }
2108 
2109             WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2110             DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2111             documentActions.superUserTakeRequestedAction(parameters, true, actionRequestId);
2112             String messageString;
2113             if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.ACKNOWLEDGE.getCode())) {
2114                 messageString = "general.routing.superuser.actionRequestAcknowledged";
2115             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.FYI.getCode())) {
2116                 messageString = "general.routing.superuser.actionRequestFYI";
2117             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.COMPLETE.getCode())) {
2118                 messageString = "general.routing.superuser.actionRequestCompleted";
2119             } else if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.APPROVE.getCode())) {
2120                 messageString = "general.routing.superuser.actionRequestApproved";
2121             } else {
2122                 messageString = "general.routing.superuser.actionRequestApproved";
2123             }
2124             GlobalVariables.getMessageMap().putInfo("document", messageString, documentForm.getDocId(), actionRequestId);
2125         }
2126         documentForm.setSuperUserAnnotation("");
2127         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2128     }
2129 
2130     public ActionForward superUserDisapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2131     	KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2132     	if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2133     		GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.annotation.missing", "");
2134     		return mapping.findForward(RiceConstants.MAPPING_BASIC);
2135     	} else if (!documentForm.getSelectedActionRequests().isEmpty()) {
2136             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.when.actions.checked", "");
2137             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2138         } else if (!documentForm.isStateAllowsApproveOrDisapprove()) {
2139             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.disapprove.not.allowed", "");
2140             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2141         }
2142 
2143         WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2144         DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2145         documentActions.superUserDisapprove(parameters, true);
2146         GlobalVariables.getMessageMap().putInfo("document", "general.routing.superuser.disapproved", documentForm.getDocId());
2147         documentForm.setSuperUserAnnotation("");
2148         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2149     }
2150 
2151     public ActionForward superUserApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
2152         KualiDocumentFormBase documentForm = (KualiDocumentFormBase)form;
2153         if(StringUtils.isBlank(documentForm.getSuperUserAnnotation())) {
2154             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.annotation.missing", "");
2155             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2156         } else if (!documentForm.getSelectedActionRequests().isEmpty()) {
2157             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.when.actions.checked", "");
2158             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2159         } else if (!documentForm.isStateAllowsApproveOrDisapprove()) {
2160             GlobalVariables.getMessageMap().putErrorForSectionId("superuser.errors", "superuser.approve.not.allowed", "");
2161             return mapping.findForward(RiceConstants.MAPPING_BASIC);
2162         }
2163 
2164         WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentForm.getWorkflowDocument().getDocumentTypeId());
2165         DocumentActionParameters parameters = DocumentActionParameters.create(documentForm.getDocId(), GlobalVariables.getUserSession().getPrincipalId(), documentForm.getSuperUserAnnotation());
2166         documentActions.superUserBlanketApprove(parameters, true);
2167         GlobalVariables.getMessageMap().putInfo("document", "general.routing.superuser.approved", documentForm.getDocId());
2168         documentForm.setSuperUserAnnotation("");
2169         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2170     }
2171 
2172     private WorkflowDocumentActionsService getWorkflowDocumentActionsService(String documentTypeId) {
2173         DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(documentTypeId);
2174         String applicationId = documentType.getApplicationId();
2175         QName serviceName = new QName(KewApiConstants.Namespaces.KEW_NAMESPACE_2_0,
2176                 KewApiConstants.ServiceNames.WORKFLOW_DOCUMENT_ACTIONS_SERVICE_SOAP);
2177         WorkflowDocumentActionsService service = (WorkflowDocumentActionsService) KsbApiServiceLocator.getServiceBus()
2178                 .getService(serviceName, applicationId);
2179         if (service == null) {
2180             service = KewApiServiceLocator.getWorkflowDocumentActionsService();
2181         }
2182         return service;
2183     }
2184     
2185     /**
2186      * Complete document action
2187      * 
2188      * @param mapping
2189      * @param form
2190      * @param request
2191      * @param response
2192      * @return
2193      * @throws Exception
2194      */
2195     public ActionForward complete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
2196         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
2197         doProcessingAfterPost(kualiDocumentFormBase, request);
2198 
2199         kualiDocumentFormBase.setDerivedValuesOnForm(request);
2200         ActionForward preRulesForward = promptBeforeValidation(mapping, form, request, response);
2201         if (preRulesForward != null) {
2202             return preRulesForward;
2203         }
2204 
2205         Document document = kualiDocumentFormBase.getDocument();
2206 
2207         getDocumentService().completeDocument(document, kualiDocumentFormBase.getAnnotation(), combineAdHocRecipients(kualiDocumentFormBase));
2208         KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL);
2209         kualiDocumentFormBase.setAnnotation("");
2210 
2211         return mapping.findForward(RiceConstants.MAPPING_BASIC);
2212     }
2213     
2214     /**
2215      * KULRICE-7864: blanket approve should not be allowed when adhoc route for completion request is newly added 
2216      * 
2217      * determine whether any adhoc recipient in the given document has been just added for completion action
2218      */
2219     protected boolean hasPendingAdhocForCompletion(KualiDocumentFormBase kualiDocumentFormBase){
2220         List<AdHocRouteRecipient> adHocRecipients = this.combineAdHocRecipients(kualiDocumentFormBase);
2221         
2222         for(AdHocRouteRecipient receipients : adHocRecipients){
2223             String actionRequestedCode = receipients.getActionRequested();
2224             
2225             if(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestedCode)){
2226                 return true;
2227             }
2228         }
2229         
2230         return false;
2231     }
2232     
2233 }
2234