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