View Javadoc

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