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