View Javadoc

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