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