View Javadoc

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