View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.web.controller;
17  
18  import org.apache.commons.lang.ArrayUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.config.property.ConfigurationService;
22  import org.kuali.rice.core.api.exception.RiceRuntimeException;
23  import org.kuali.rice.core.api.util.RiceKeyConstants;
24  import org.kuali.rice.kew.api.KewApiConstants;
25  import org.kuali.rice.kew.api.WorkflowDocument;
26  import org.kuali.rice.kew.api.exception.WorkflowException;
27  import org.kuali.rice.kim.api.identity.Person;
28  import org.kuali.rice.krad.UserSessionUtils;
29  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
30  import org.kuali.rice.krad.bo.Attachment;
31  import org.kuali.rice.krad.bo.DocumentHeader;
32  import org.kuali.rice.krad.bo.Note;
33  import org.kuali.rice.krad.document.Document;
34  import org.kuali.rice.krad.document.DocumentAuthorizer;
35  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
36  import org.kuali.rice.krad.exception.DocumentAuthorizationException;
37  import org.kuali.rice.krad.exception.UnknownDocumentIdException;
38  import org.kuali.rice.krad.exception.ValidationException;
39  import org.kuali.rice.krad.rules.rule.event.AddNoteEvent;
40  import org.kuali.rice.krad.service.AttachmentService;
41  import org.kuali.rice.krad.service.BusinessObjectService;
42  import org.kuali.rice.krad.service.DataDictionaryService;
43  import org.kuali.rice.krad.service.DocumentDictionaryService;
44  import org.kuali.rice.krad.service.DocumentService;
45  import org.kuali.rice.krad.service.KRADServiceLocator;
46  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
47  import org.kuali.rice.krad.service.NoteService;
48  import org.kuali.rice.krad.uif.UifConstants.WorkflowAction;
49  import org.kuali.rice.krad.uif.UifParameters;
50  import org.kuali.rice.krad.uif.UifPropertyPaths;
51  import org.kuali.rice.krad.uif.container.CollectionGroup;
52  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
53  import org.kuali.rice.krad.util.GlobalVariables;
54  import org.kuali.rice.krad.util.KRADConstants;
55  import org.kuali.rice.krad.util.NoteType;
56  import org.kuali.rice.krad.web.form.DocumentFormBase;
57  import org.kuali.rice.krad.web.form.UifFormBase;
58  import org.springframework.util.FileCopyUtils;
59  import org.springframework.validation.BindingResult;
60  import org.springframework.web.bind.ServletRequestBindingException;
61  import org.springframework.web.bind.annotation.ModelAttribute;
62  import org.springframework.web.bind.annotation.RequestMapping;
63  import org.springframework.web.bind.annotation.RequestMethod;
64  import org.springframework.web.multipart.MultipartFile;
65  import org.springframework.web.servlet.ModelAndView;
66  
67  import javax.servlet.http.HttpServletRequest;
68  import javax.servlet.http.HttpServletResponse;
69  import java.io.FileNotFoundException;
70  import java.io.IOException;
71  import java.io.InputStream;
72  import java.util.ArrayList;
73  import java.util.List;
74  import java.util.Properties;
75  
76  /**
77   * Base controller class for all KRAD <code>DocumentView</code> screens working
78   * with <code>Document</code> models
79   *
80   * <p>
81   * Provides default controller implementations for the standard document actions including: doc handler
82   * (retrieve from doc search and action list), save, route (and other KEW actions)
83   * </p>
84   *
85   * @author Kuali Rice Team (rice.collab@kuali.org)
86   */
87  public abstract class DocumentControllerBase extends UifControllerBase {
88      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentControllerBase.class);
89  
90      // COMMAND constants which cause docHandler to load an existing document
91      // instead of creating a new one
92      protected static final String[] DOCUMENT_LOAD_COMMANDS =
93              {KewApiConstants.ACTIONLIST_COMMAND, KewApiConstants.DOCSEARCH_COMMAND, KewApiConstants.SUPERUSER_COMMAND,
94                      KewApiConstants.HELPDESK_ACTIONLIST_COMMAND};
95  
96      private BusinessObjectService businessObjectService;
97      private DataDictionaryService dataDictionaryService;
98      private DocumentService documentService;
99      private DocumentDictionaryService documentDictionaryService;
100     private AttachmentService attachmentService;
101     private NoteService noteService;
102 
103     /**
104      * @see org.kuali.rice.krad.web.controller.UifControllerBase#createInitialForm(javax.servlet.http.HttpServletRequest)
105      */
106     @Override
107     protected abstract DocumentFormBase createInitialForm(HttpServletRequest request);
108 
109     /**
110      * Used to funnel all document handling through, we could do useful things
111      * like log and record various openings and status Additionally it may be
112      * nice to have a single dispatcher that can know how to dispatch to a
113      * redirect url for document specific handling but we may not need that as
114      * all we should need is the document to be able to load itself based on
115      * document id and then which action forward or redirect is pertinent for
116      * the document type.
117      */
118     @RequestMapping(params = "methodToCall=docHandler")
119     public ModelAndView docHandler(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
120             HttpServletRequest request, HttpServletResponse response) throws Exception {
121         String command = form.getCommand();
122 
123         // in all of the following cases we want to load the document
124         if (ArrayUtils.contains(DOCUMENT_LOAD_COMMANDS, command) && form.getDocId() != null) {
125             loadDocument(form);
126         } else if (KewApiConstants.INITIATE_COMMAND.equals(command)) {
127             createDocument(form);
128         } else {
129             LOG.error("docHandler called with invalid parameters");
130             throw new IllegalArgumentException("docHandler called with invalid parameters");
131         }
132 
133         // TODO: authorization on document actions
134         // if (KEWConstants.SUPERUSER_COMMAND.equalsIgnoreCase(command)) {
135         // form.setSuppressAllButtons(true);
136         // }
137 
138         return getUIFModelAndView(form);
139     }
140 
141     /**
142      * Loads the document by its provided document header id. This has been abstracted out so that
143      * it can be overridden in children if the need arises
144      *
145      * @param form - form instance that contains the doc id parameter and where
146      * the retrieved document instance should be set
147      */
148     protected void loadDocument(DocumentFormBase form) throws WorkflowException {
149         String docId = form.getDocId();
150 
151         LOG.debug("Loading document" + docId);
152 
153         Document doc = null;
154         doc = getDocumentService().getByDocumentHeaderId(docId);
155         if (doc == null) {
156             throw new UnknownDocumentIdException(
157                     "Document no longer exists.  It may have been cancelled before being saved.");
158         }
159 
160         WorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
161         if (!getDocumentDictionaryService().getDocumentAuthorizer(doc).canOpen(doc,
162                 GlobalVariables.getUserSession().getPerson())) {
163             throw buildAuthorizationException("open", doc);
164         }
165 
166         // re-retrieve the document using the current user's session - remove
167         // the system user from the WorkflowDcument object
168         if (workflowDocument != doc.getDocumentHeader().getWorkflowDocument()) {
169             LOG.warn("Workflow document changed via canOpen check");
170             doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
171         }
172 
173         form.setDocument(doc);
174         WorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
175         form.setDocTypeName(workflowDoc.getDocumentTypeName());
176 
177         UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDoc);
178     }
179 
180     /**
181      * Creates a new document of the type specified by the docTypeName property of the given form.
182      * This has been abstracted out so that it can be overridden in children if the need arises.
183      *
184      * @param form - form instance that contains the doc type parameter and where
185      * the new document instance should be set
186      */
187     protected void createDocument(DocumentFormBase form) throws WorkflowException {
188         LOG.debug("Creating new document instance for doc type: " + form.getDocTypeName());
189         Document doc = getDocumentService().getNewDocument(form.getDocTypeName());
190 
191         form.setDocument(doc);
192         form.setDocTypeName(doc.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
193     }
194 
195     /**
196      * Reloads the document contained on the form from the database
197      *
198      * @param form - document form base containing the document instance from which the document number will
199      * be retrieved and used to fetch the document from the database
200      * @return ModelAndView
201      */
202     @RequestMapping(params = "methodToCall=reload")
203     public ModelAndView reload(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
204             HttpServletRequest request, HttpServletResponse response) throws Exception {
205         Document document = form.getDocument();
206 
207         // prepare for the reload action - set doc id and command
208         form.setDocId(document.getDocumentNumber());
209         form.setCommand(DOCUMENT_LOAD_COMMANDS[1]);
210 
211         GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, RiceKeyConstants.MESSAGE_RELOADED);
212 
213         // forward off to the doc handler
214         return docHandler(form, result, request, response);
215     }
216 
217     /**
218      * Prompts user to confirm the cancel action then if confirmed cancels the document instance
219      * contained on the form
220      *
221      * @param form - document form base containing the document instance that will be cancelled
222      * @return ModelAndView
223      */
224     @RequestMapping(params = "methodToCall=cancel")
225     @Override
226     public ModelAndView cancel(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
227             HttpServletRequest request, HttpServletResponse response) {
228         DocumentFormBase documentForm = (DocumentFormBase) form;
229 
230         // TODO: prompt user to confirm the cancel, need question framework
231 
232         performWorkflowAction(documentForm, WorkflowAction.CANCEL, false);
233 
234         return returnToPrevious(form);
235     }
236 
237     /**
238      * Saves the document instance contained on the form
239      *
240      * @param form - document form base containing the document instance that will be saved
241      * @return ModelAndView
242      */
243     @RequestMapping(params = "methodToCall=save")
244     public ModelAndView save(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
245             HttpServletRequest request, HttpServletResponse response) throws Exception {
246         performWorkflowAction(form, WorkflowAction.SAVE, true);
247 
248         return getUIFModelAndView(form);
249     }
250 
251     /**
252      * Completes the document instance contained on the form
253      *
254      * @param form - document form base containing the document instance that will be completed
255      * @return ModelAndView
256      */
257     @RequestMapping(params = "methodToCall=complete")
258     public ModelAndView complete(@ModelAttribute("KualiForm")
259     DocumentFormBase form, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception {
260         performWorkflowAction(form, WorkflowAction.COMPLETE, true);
261 
262         return getUIFModelAndView(form);
263     }
264 
265     /**
266      * Routes the document instance contained on the form
267      *
268      * @param form - document form base containing the document instance that will be routed
269      * @return ModelAndView
270      */
271     @RequestMapping(params = "methodToCall=route")
272     public ModelAndView route(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
273             HttpServletRequest request, HttpServletResponse response) {
274         performWorkflowAction(form, WorkflowAction.ROUTE, true);
275 
276         return getUIFModelAndView(form);
277     }
278 
279     /**
280      * Performs the blanket approve workflow action on the form document instance
281      *
282      * @param form - document form base containing the document instance that will be blanket approved
283      * @return ModelAndView
284      */
285     @RequestMapping(params = "methodToCall=blanketApprove")
286     public ModelAndView blanketApprove(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
287             HttpServletRequest request, HttpServletResponse response) throws Exception {
288         performWorkflowAction(form, WorkflowAction.BLANKETAPPROVE, true);
289 
290         return getUIFModelAndView(form);
291     }
292 
293     /**
294      * Performs the approve workflow action on the form document instance
295      *
296      * @param form - document form base containing the document instance that will be approved
297      * @return ModelAndView
298      */
299     @RequestMapping(params = "methodToCall=approve")
300     public ModelAndView approve(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
301             HttpServletRequest request, HttpServletResponse response) throws Exception {
302         performWorkflowAction(form, WorkflowAction.APPROVE, true);
303 
304         return returnToPrevious(form);
305     }
306 
307     /**
308      * Performs the disapprove workflow action on the form document instance
309      *
310      * @param form - document form base containing the document instance that will be disapproved
311      * @return ModelAndView
312      */
313     @RequestMapping(params = "methodToCall=disapprove")
314     public ModelAndView disapprove(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
315             HttpServletRequest request, HttpServletResponse response) throws Exception {
316         // TODO: need to prompt for disapproval note text
317         performWorkflowAction(form, WorkflowAction.DISAPPROVE, true);
318 
319         return returnToPrevious(form);
320     }
321 
322     /**
323      * Performs the fyi workflow action on the form document instance
324      *
325      * @param form - document form base containing the document instance the fyi will be taken on
326      * @return ModelAndView
327      */
328     @RequestMapping(params = "methodToCall=fyi")
329     public ModelAndView fyi(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
330             HttpServletRequest request, HttpServletResponse response) throws Exception {
331         performWorkflowAction(form, WorkflowAction.FYI, false);
332 
333         return returnToPrevious(form);
334     }
335 
336     /**
337      * Performs the acknowledge workflow action on the form document instance
338      *
339      * @param form - document form base containing the document instance the acknowledge will be taken on
340      * @return ModelAndView
341      */
342     @RequestMapping(params = "methodToCall=acknowledge")
343     public ModelAndView acknowledge(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
344             HttpServletRequest request, HttpServletResponse response) throws Exception {
345         performWorkflowAction(form, WorkflowAction.ACKNOWLEDGE, false);
346 
347         return returnToPrevious(form);
348     }
349 
350     /**
351      * Sends a AdHoc Request of the document instance contained on the form to the AdHoc Recipients
352      *
353      * @param form - document form base containing the document instance that will be sent
354      * @return ModelAndView
355      */
356     @RequestMapping(params = "methodToCall=sendAdHocRequests")
357     public ModelAndView sendAdHocRequests(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
358                               HttpServletRequest request, HttpServletResponse response) {
359         performWorkflowAction(form, WorkflowAction.SENDADHOCREQUESTS, true);
360 
361         return getUIFModelAndView(form);
362     }
363 
364     /**
365      * Invokes the {@link DocumentService} to carry out a request workflow action and adds a success message, if
366      * requested a check for sensitive data is also performed
367      *
368      * @param form - document form instance containing the document for which the action will be taken on
369      * @param action - {@link WorkflowAction} enum indicating what workflow action to take
370      * @param checkSensitiveData - boolean indicating whether a check for sensitive data should occur
371      */
372     protected void performWorkflowAction(DocumentFormBase form, WorkflowAction action, boolean checkSensitiveData) {
373         Document document = form.getDocument();
374 
375         LOG.debug("Performing workflow action " + action.name() + "for document: " + document.getDocumentNumber());
376 
377         // TODO: need question and prompt framework
378         if (checkSensitiveData) {
379             //        String viewName = checkAndWarnAboutSensitiveData(form, request, response,
380             //                KRADPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "route", "");
381             //        if (viewName != null) {
382             //            return new ModelAndView(viewName);
383             //        }
384         }
385 
386         try {
387             String successMessageKey = null;
388             switch (action) {
389                 case SAVE:
390                     getDocumentService().saveDocument(document);
391                     successMessageKey = RiceKeyConstants.MESSAGE_SAVED;
392                     break;
393                 case ROUTE:
394                     getDocumentService().routeDocument(document, form.getAnnotation(), combineAdHocRecipients(form));
395                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL;
396                     break;
397                 case BLANKETAPPROVE:
398                     getDocumentService().blanketApproveDocument(document, form.getAnnotation(), combineAdHocRecipients(
399                             form));
400                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_APPROVED;
401                     break;
402                 case APPROVE:
403                     getDocumentService().approveDocument(document, form.getAnnotation(), combineAdHocRecipients(form));
404                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_APPROVED;
405                     break;
406                 case DISAPPROVE:
407                     // TODO: need to get disapprove note from user
408                     String disapprovalNoteText = generateDisapprovalNote(form, checkSensitiveData);
409                     getDocumentService().disapproveDocument(document, disapprovalNoteText);
410                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_DISAPPROVED;
411                     break;
412                 case FYI:
413                     getDocumentService().clearDocumentFyi(document, combineAdHocRecipients(form));
414                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_FYIED;
415                     break;
416                 case ACKNOWLEDGE:
417                     getDocumentService().acknowledgeDocument(document, form.getAnnotation(), combineAdHocRecipients(
418                             form));
419                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_ACKNOWLEDGED;
420                     break;
421                 case CANCEL:
422                     if (getDocumentService().documentExists(document.getDocumentNumber())) {
423                         getDocumentService().cancelDocument(document, form.getAnnotation());
424                         successMessageKey = RiceKeyConstants.MESSAGE_CANCELLED;
425                     }
426                     break;
427                 case COMPLETE:
428                     if (getDocumentService().documentExists(document.getDocumentNumber())) {
429                         getDocumentService().completeDocument(document, form.getAnnotation(), combineAdHocRecipients(form));
430                         successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL;
431                     }
432                     break;
433                 case SENDADHOCREQUESTS:
434                     getDocumentService().sendAdHocRequests(document, form.getAnnotation(), combineAdHocRecipients(form));
435                     successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL;
436                     break;
437             }
438 
439             if (successMessageKey != null) {
440                 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey);
441             }
442         } catch (ValidationException e) {
443             // if errors in map, swallow exception so screen will draw with errors
444             // if not then throw runtime because something bad happened
445             if (GlobalVariables.getMessageMap().hasNoErrors()) {
446                 throw new RiceRuntimeException("Validation Exception with no error message.", e);
447             }
448         } catch (Exception e) {
449             throw new RiceRuntimeException(
450                     "Exception trying to invoke action " + action.name() + "for document: " + document
451                             .getDocumentNumber(), e);
452         }
453 
454         form.setAnnotation("");
455     }
456 
457     /**
458      * Redirects to the supervisor functions page
459      *
460      * @return ModelAndView - model and view configured for the redirect URL
461      */
462     @RequestMapping(params = "methodToCall=supervisorFunctions")
463     public ModelAndView supervisorFunctions(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
464             HttpServletRequest request, HttpServletResponse response) throws Exception {
465 
466         String workflowSuperUserUrl = getConfigurationService().getPropertyValueAsString(KRADConstants.WORKFLOW_URL_KEY)
467                 + "/" + KRADConstants.SUPERUSER_ACTION;
468 
469         Properties props = new Properties();
470         props.put(UifParameters.METHOD_TO_CALL, "displaySuperUserDocument");
471         props.put(UifPropertyPaths.DOCUMENT_ID, form.getDocument().getDocumentNumber());
472 
473         return performRedirect(form, workflowSuperUserUrl, props);
474     }
475 
476     /**
477      * Called by the add note action for adding a note. Method validates, saves attachment and adds the
478      * time stamp and author. Calls the UifControllerBase.addLine method to handle generic actions.
479      *
480      * @param uifForm - document form base containing the note instance that will be inserted into the document
481      * @return ModelAndView
482      */
483     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=insertNote")
484     public ModelAndView insertNote(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
485             HttpServletRequest request, HttpServletResponse response) {
486 
487         // Get the note add line
488         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
489         CollectionGroup collectionGroup = uifForm.getPostedView().getViewIndex().getCollectionGroupByPath(
490                 selectedCollectionPath);
491         String addLinePath = collectionGroup.getAddLineBindingInfo().getBindingPath();
492         Object addLine = ObjectPropertyUtils.getPropertyValue(uifForm, addLinePath);
493         Note newNote = (Note) addLine;
494         newNote.setNotePostedTimestampToCurrent();
495 
496         Document document = ((DocumentFormBase) uifForm).getDocument();
497 
498         newNote.setRemoteObjectIdentifier(document.getNoteTarget().getObjectId());
499 
500         // Get the attachment file
501         String attachmentTypeCode = null;
502         MultipartFile attachmentFile = uifForm.getAttachmentFile();
503         Attachment attachment = null;
504         if (attachmentFile != null && !StringUtils.isBlank(attachmentFile.getOriginalFilename())) {
505             if (attachmentFile.getSize() == 0) {
506                 GlobalVariables.getMessageMap().putError(String.format("%s.%s",
507                         KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME,
508                         KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME), RiceKeyConstants.ERROR_UPLOADFILE_EMPTY,
509                         attachmentFile.getOriginalFilename());
510             } else {
511                 if (newNote.getAttachment() != null) {
512                     attachmentTypeCode = newNote.getAttachment().getAttachmentTypeCode();
513                 }
514 
515                 DocumentAuthorizer documentAuthorizer = getDocumentDictionaryService().getDocumentAuthorizer(document);
516                 if (!documentAuthorizer.canAddNoteAttachment(document, attachmentTypeCode,
517                         GlobalVariables.getUserSession().getPerson())) {
518                     throw buildAuthorizationException("annotate", document);
519                 }
520 
521                 try {
522                     String attachmentType = null;
523                     Attachment newAttachment = newNote.getAttachment();
524                     if (newAttachment != null) {
525                         attachmentType = newAttachment.getAttachmentTypeCode();
526                     }
527 
528                     attachment = getAttachmentService().createAttachment(document.getNoteTarget(),
529                             attachmentFile.getOriginalFilename(), attachmentFile.getContentType(),
530                             (int) attachmentFile.getSize(), attachmentFile.getInputStream(), attachmentType);
531                 } catch (IOException e) {
532                     throw new RiceRuntimeException("Unable to store attachment", e);
533                 }
534             }
535         }
536 
537         Person kualiUser = GlobalVariables.getUserSession().getPerson();
538         if (kualiUser == null) {
539             throw new IllegalStateException("Current UserSession has a null Person.");
540         }
541 
542         newNote.setAuthorUniversalIdentifier(kualiUser.getPrincipalId());
543 
544         // validate the note
545         boolean rulePassed = KRADServiceLocatorWeb.getKualiRuleService().applyRules(new AddNoteEvent(document,
546                 newNote));
547 
548         // if the rule evaluation passed, let's add the note; otherwise, return with an error
549         if (rulePassed) {
550             newNote.refresh();
551 
552             DocumentHeader documentHeader = document.getDocumentHeader();
553 
554             // adding the attachment after refresh gets called, since the attachment record doesn't get persisted
555             // until the note does (and therefore refresh doesn't have any attachment to autoload based on the id, nor does it
556             // autopopulate the id since the note hasn't been persisted yet)
557             if (attachment != null) {
558                 newNote.addAttachment(attachment);
559             }
560             // Save the note if the document is already saved
561             if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty(
562                     document.getNoteTarget().getObjectId()) && !(document instanceof MaintenanceDocument && NoteType
563                     .BUSINESS_OBJECT.getCode().equals(newNote.getNoteTypeCode()))) {
564 
565                 getNoteService().save(newNote);
566             }
567 
568             return addLine(uifForm, result, request, response);
569         } else {
570             return getUIFModelAndView(uifForm);
571         }
572     }
573 
574     /**
575      * Called by the delete note action for deleting a note.
576      * Calls the UifControllerBase.deleteLine method to handle
577      * generic actions.
578      */
579     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=deleteNote")
580     public ModelAndView deleteNote(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
581             HttpServletRequest request, HttpServletResponse response) {
582 
583         String selectedLineIndex = uifForm.getActionParamaterValue("selectedLineIndex");
584         Document document = ((DocumentFormBase) uifForm).getDocument();
585         Note note = document.getNote(Integer.parseInt(selectedLineIndex));
586 
587         Attachment attachment = note.getAttachment();
588         String attachmentTypeCode = null;
589         if (attachment != null) {
590             attachmentTypeCode = attachment.getAttachmentTypeCode();
591         }
592 
593         // check delete note authorization
594         Person user = GlobalVariables.getUserSession().getPerson();
595         String authorUniversalIdentifier = note.getAuthorUniversalIdentifier();
596         if (!getDocumentDictionaryService().getDocumentAuthorizer(document).canDeleteNoteAttachment(document,
597                 attachmentTypeCode, authorUniversalIdentifier, user)) {
598             throw buildAuthorizationException("annotate", document);
599         }
600 
601         if (attachment != null && attachment.isComplete()) { // only do this if the note has been persisted
602             //KFSMI-798 - refresh() changed to refreshNonUpdateableReferences()
603             //All references for the business object Attachment are auto-update="none",
604             //so refreshNonUpdateableReferences() should work the same as refresh()
605             if (note.getNoteIdentifier()
606                     != null) { // KULRICE-2343 don't blow away note reference if the note wasn't persisted
607                 attachment.refreshNonUpdateableReferences();
608             }
609             getAttachmentService().deleteAttachmentContents(attachment);
610         }
611         // delete the note if the document is already saved
612         if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
613             getNoteService().deleteNote(note);
614         }
615 
616         return deleteLine(uifForm, result, request, response);
617     }
618 
619     /**
620      * Called by the download attachment action on a note. Method
621      * gets the attachment input stream from the AttachmentService
622      * and writes it to the request output stream.
623      */
624     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=downloadAttachment")
625     public ModelAndView downloadAttachment(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
626             HttpServletRequest request,
627             HttpServletResponse response) throws ServletRequestBindingException, FileNotFoundException, IOException {
628         // Get the attachment input stream
629         String selectedLineIndex = uifForm.getActionParamaterValue("selectedLineIndex");
630         Note note = ((DocumentFormBase) uifForm).getDocument().getNote(Integer.parseInt(selectedLineIndex));
631         Attachment attachment = note.getAttachment();
632         InputStream is = getAttachmentService().retrieveAttachmentContents(attachment);
633 
634         // Set the response headers
635         response.setContentType(attachment.getAttachmentMimeTypeCode());
636         response.setContentLength(attachment.getAttachmentFileSize().intValue());
637         response.setHeader("Expires", "0");
638         response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
639         response.setHeader("Pragma", "public");
640         response.setHeader("Content-Disposition",
641                 "attachment; filename=\"" + attachment.getAttachmentFileName() + "\"");
642 
643         // Copy the input stream to the response
644         FileCopyUtils.copy(is, response.getOutputStream());
645         return null;
646     }
647 
648     /**
649      * Called by the cancel attachment action on a note. Method
650      * removes the attachment file from the form.
651      */
652     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=cancelAttachment")
653     public ModelAndView cancelAttachment(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
654             HttpServletRequest request, HttpServletResponse response) {
655         // Remove the attached file
656         uifForm.setAttachmentFile(null);
657         return getUIFModelAndView(uifForm);
658     }
659 
660     /**
661      * Checks if the given value matches patterns that indicate sensitive data
662      * and if configured to give a warning for sensitive data will prompt the
663      * user to continue.
664      *
665      * @param form
666      * @param request
667      * @param response
668      * @param fieldName - name of field with value being checked
669      * @param fieldValue - value to check for sensitive data
670      * @param caller - method that should be called back from question
671      * @param context - additional context that needs to be passed back with the
672      * question response
673      * @return - view for spring to forward to, or null if processing should
674      *         continue
675      * @throws Exception
676      */
677     protected String checkAndWarnAboutSensitiveData(DocumentFormBase form, HttpServletRequest request,
678             HttpServletResponse response, String fieldName, String fieldValue, String caller,
679             String context) throws Exception {
680 
681         String viewName = null;
682         Document document = form.getDocument();
683 
684         // TODO: need to move containsSensitiveDataPatternMatch to util class in krad
685 //        boolean containsSensitiveData = false;
686 //        //boolean containsSensitiveData = WebUtils.containsSensitiveDataPatternMatch(fieldValue);
687 //
688 //        // check if warning is configured in which case we will prompt, or if
689 //        // not business rules will thrown an error
690 //        boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
691 //                KRADConstants.KRAD_NAMESPACE, ParameterConstants.ALL_COMPONENT,
692 //                KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
693 //
694 //        // determine if the question has been asked yet
695 //        Map<String, String> ticketContext = new HashMap<String, String>();
696 //        ticketContext.put(KRADPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
697 //        ticketContext.put(KRADConstants.CALLING_METHOD, caller);
698 //        ticketContext.put(KRADPropertyConstants.NAME, fieldName);
699 //
700 //        boolean questionAsked = GlobalVariables.getUserSession().hasMatchingSessionTicket(
701 //                KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET, ticketContext);
702 //
703 //        // start in logic for confirming the sensitive data
704 //        if (containsSensitiveData && warnForSensitiveData && !questionAsked) {
705 //            Object question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
706 //            if (question == null || !KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
707 //
708 //                // TODO not ready for question framework yet
709 //                /*
710 //                     * // question hasn't been asked, prompt to continue return
711 //                     * this.performQuestionWithoutInput(mapping, form, request,
712 //                     * response, KRADConstants.DOCUMENT_SENSITIVE_DATA_QUESTION,
713 //                     * getKualiConfigurationService()
714 //                     * .getPropertyValueAsString(RiceKeyConstants
715 //                     * .QUESTION_SENSITIVE_DATA_DOCUMENT),
716 //                     * KRADConstants.CONFIRMATION_QUESTION, caller, context);
717 //                     */
718 //                viewName = "ask_user_questions";
719 //            } else {
720 //                Object buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
721 //
722 //                // if no button clicked just reload the doc
723 //                if (ConfirmationQuestion.NO.equals(buttonClicked)) {
724 //                    // TODO figure out what to return
725 //                    viewName = "user_says_no";
726 //                }
727 //
728 //                // answered yes, create session ticket so we not to ask question
729 //                // again if there are further question requests
730 //                SessionTicket ticket = new SessionTicket(KRADConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET);
731 //                ticket.setTicketContext(ticketContext);
732 //                GlobalVariables.getUserSession().putSessionTicket(ticket);
733 //            }
734 //        }
735 
736         // returning null will indicate processing should continue (no redirect)
737         return viewName;
738     }
739 
740     /**
741      * Convenience method to combine the two lists of ad hoc recipients into one which should be done before
742      * calling any of the document service methods that expect a list of ad hoc recipients
743      *
744      * @param form - document form instance containing the ad hod lists
745      * @return List<AdHocRouteRecipient> combined ad hoc recipients
746      */
747     protected List<AdHocRouteRecipient> combineAdHocRecipients(DocumentFormBase form) {
748         Document document = form.getDocument();
749 
750         List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>();
751         adHocRecipients.addAll(document.getAdHocRoutePersons());
752         adHocRecipients.addAll(document.getAdHocRouteWorkgroups());
753 
754         return adHocRecipients;
755     }
756 
757     /**
758      * Convenience method for building authorization exceptions
759      *
760      * @param action - the action that was requested
761      * @param document - document instance the action was requested for
762      */
763     protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) {
764         return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(),
765                 action, document.getDocumentNumber());
766     }
767 
768     public BusinessObjectService getBusinessObjectService() {
769         if (this.businessObjectService == null) {
770             this.businessObjectService = KRADServiceLocator.getBusinessObjectService();
771         }
772         return this.businessObjectService;
773     }
774 
775     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
776         this.businessObjectService = businessObjectService;
777     }
778 
779     public DataDictionaryService getDataDictionaryService() {
780         if (this.dataDictionaryService == null) {
781             this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
782         }
783         return this.dataDictionaryService;
784     }
785 
786     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
787         this.dataDictionaryService = dataDictionaryService;
788     }
789 
790     public DocumentService getDocumentService() {
791         if (this.documentService == null) {
792             this.documentService = KRADServiceLocatorWeb.getDocumentService();
793         }
794         return this.documentService;
795     }
796 
797     public void setDocumentService(DocumentService documentService) {
798         this.documentService = documentService;
799     }
800 
801     public DocumentDictionaryService getDocumentDictionaryService() {
802         if (this.documentDictionaryService == null) {
803             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
804         }
805         return this.documentDictionaryService;
806     }
807 
808     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
809         this.documentDictionaryService = documentDictionaryService;
810     }
811 
812     public AttachmentService getAttachmentService() {
813         if (attachmentService == null) {
814             attachmentService = KRADServiceLocator.getAttachmentService();
815         }
816         return this.attachmentService;
817     }
818 
819     public NoteService getNoteService() {
820         if (noteService == null) {
821             noteService = KRADServiceLocator.getNoteService();
822         }
823 
824         return this.noteService;
825     }
826 
827     public ConfigurationService getConfigurationService() {
828         return CoreApiServiceLocator.getKualiConfigurationService();
829     }
830 
831     protected String generateDisapprovalNote(DocumentFormBase form, boolean checkSensitiveData) {
832         return "";
833     }
834 
835 }