View Javadoc

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