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