View Javadoc

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