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