View Javadoc

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