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