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