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