001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.document; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.ArrayUtils; 020import org.apache.commons.lang.StringUtils; 021import org.kuali.rice.core.api.CoreApiServiceLocator; 022import org.kuali.rice.core.api.config.property.ConfigContext; 023import org.kuali.rice.core.api.config.property.ConfigurationService; 024import org.kuali.rice.core.api.exception.RiceRuntimeException; 025import org.kuali.rice.core.api.util.RiceKeyConstants; 026import org.kuali.rice.coreservice.framework.parameter.ParameterConstants; 027import org.kuali.rice.coreservice.framework.parameter.ParameterService; 028import org.kuali.rice.kew.api.KewApiConstants; 029import org.kuali.rice.kew.api.KewApiServiceLocator; 030import org.kuali.rice.kew.api.WorkflowDocument; 031import org.kuali.rice.kew.api.action.ActionRequest; 032import org.kuali.rice.kew.api.action.ActionRequestType; 033import org.kuali.rice.kew.api.action.DocumentActionParameters; 034import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService; 035import org.kuali.rice.kew.api.doctype.DocumentType; 036import org.kuali.rice.kew.api.exception.WorkflowException; 037import org.kuali.rice.kim.api.identity.Person; 038import org.kuali.rice.krad.UserSessionUtils; 039import org.kuali.rice.krad.bo.AdHocRouteRecipient; 040import org.kuali.rice.krad.bo.Attachment; 041import org.kuali.rice.krad.bo.DocumentHeader; 042import org.kuali.rice.krad.bo.Note; 043import org.kuali.rice.krad.exception.DocumentAuthorizationException; 044import org.kuali.rice.krad.exception.UnknownDocumentIdException; 045import org.kuali.rice.krad.exception.ValidationException; 046import org.kuali.rice.krad.maintenance.MaintenanceDocument; 047import org.kuali.rice.krad.rules.rule.event.AddNoteEvent; 048import org.kuali.rice.krad.rules.rule.event.DocumentEvent; 049import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent; 050import org.kuali.rice.krad.rules.rule.event.SaveDocumentEvent; 051import org.kuali.rice.krad.service.AttachmentService; 052import org.kuali.rice.krad.service.DataDictionaryService; 053import org.kuali.rice.krad.service.DocumentDictionaryService; 054import org.kuali.rice.krad.service.DocumentService; 055import org.kuali.rice.krad.service.KRADServiceLocator; 056import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 057import org.kuali.rice.krad.service.LegacyDataAdapter; 058import org.kuali.rice.krad.service.NoteService; 059import org.kuali.rice.krad.uif.UifConstants; 060import org.kuali.rice.krad.uif.UifParameters; 061import org.kuali.rice.krad.uif.UifPropertyPaths; 062import org.kuali.rice.krad.uif.component.BindingInfo; 063import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 064import org.kuali.rice.krad.uif.view.DocumentView; 065import org.kuali.rice.krad.util.GlobalVariables; 066import org.kuali.rice.krad.util.KRADConstants; 067import org.kuali.rice.krad.util.KRADUtils; 068import org.kuali.rice.krad.util.NoteType; 069import org.kuali.rice.krad.web.form.DialogResponse; 070import org.kuali.rice.krad.web.form.DocumentFormBase; 071import org.kuali.rice.krad.web.form.UifFormBase; 072import org.kuali.rice.krad.web.service.CollectionControllerService; 073import org.kuali.rice.krad.web.service.ModelAndViewService; 074import org.kuali.rice.krad.web.service.NavigationControllerService; 075import org.kuali.rice.krad.web.service.impl.ControllerServiceImpl; 076import org.kuali.rice.ksb.api.KsbApiServiceLocator; 077import org.springframework.web.multipart.MultipartFile; 078import org.springframework.web.servlet.ModelAndView; 079 080import javax.servlet.http.HttpServletResponse; 081import javax.xml.namespace.QName; 082import java.io.IOException; 083import java.util.ArrayList; 084import java.util.List; 085import java.util.Properties; 086import java.util.Set; 087 088/** 089 * Default implementation of the document controller service. 090 * 091 * @author Kuali Rice Team (rice.collab@kuali.org) 092 */ 093public class DocumentControllerServiceImpl extends ControllerServiceImpl implements DocumentControllerService { 094 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( 095 DocumentControllerServiceImpl.class); 096 097 // COMMAND constants which cause docHandler to load an existing document instead of creating a new one 098 protected static final String[] DOCUMENT_LOAD_COMMANDS = 099 {KewApiConstants.ACTIONLIST_COMMAND, KewApiConstants.DOCSEARCH_COMMAND, KewApiConstants.SUPERUSER_COMMAND, 100 KewApiConstants.HELPDESK_ACTIONLIST_COMMAND}; 101 protected static final String SENSITIVE_DATA_DIALOG = "DialogGroup-SensitiveData"; 102 protected static final String EXPLANATION_DIALOG = "DisapproveExplanationDialog"; 103 104 private LegacyDataAdapter legacyDataAdapter; 105 private DataDictionaryService dataDictionaryService; 106 private DocumentService documentService; 107 private DocumentDictionaryService documentDictionaryService; 108 private AttachmentService attachmentService; 109 private NoteService noteService; 110 private ModelAndViewService modelAndViewService; 111 private NavigationControllerService navigationControllerService; 112 private ConfigurationService configurationService; 113 private CollectionControllerService collectionControllerService; 114 private ParameterService parameterService; 115 116 /** 117 * Determines whether a new document instance needs created or we need to load an existing document by 118 * checking the {@link org.kuali.rice.krad.web.form.DocumentFormBase#getCommand()} value, then delegates to 119 * a helper method to carry out the action. 120 * 121 * {@inheritDoc} 122 */ 123 @Override 124 public ModelAndView docHandler(DocumentFormBase form) throws WorkflowException { 125 String command = form.getCommand(); 126 DocumentView view = (DocumentView) form.getView(); 127 128 if (ArrayUtils.contains(DOCUMENT_LOAD_COMMANDS, command) && (form.getDocId() != null)) { 129 checkReturnLocationForDocSearch(command, form); 130 131 loadDocument(form); 132 133 if (KewApiConstants.SUPERUSER_COMMAND.equals(command)) { 134 view.setSuperUserView(true); 135 } 136 } else if (KewApiConstants.INITIATE_COMMAND.equals(command)) { 137 if (view != null) { 138 form.setApplyDefaultValues(true); 139 } 140 141 createDocument(form); 142 } else { 143 LOG.error("docHandler called with invalid parameters"); 144 throw new IllegalArgumentException("docHandler called with invalid parameters"); 145 } 146 147 return getModelAndViewService().getModelAndView(form); 148 } 149 150 /** 151 * Determines if the DOCSEARCH_COMMAND is the present command value and updates the return location to the base 152 * application url 153 * 154 * @param command the current command value 155 * @param form the form with the updated return location 156 */ 157 private void checkReturnLocationForDocSearch(String command, DocumentFormBase form) { 158 if (KewApiConstants.DOCSEARCH_COMMAND.equals(command)) { 159 form.setReturnLocation(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY)); 160 } 161 } 162 163 /** 164 * Loads the document by its provided document header id on the given form. 165 * 166 * <p>This has been abstracted out so that it can be overridden in children if the need arises</p> 167 * 168 * @param form form instance that contains the doc id parameter and where 169 * the retrieved document instance should be set 170 */ 171 protected void loadDocument(DocumentFormBase form) throws WorkflowException { 172 String docId = form.getDocId(); 173 174 if (LOG.isDebugEnabled()) { 175 LOG.debug("Loading document" + docId); 176 } 177 178 Document document = getDocumentService().getByDocumentHeaderId(docId); 179 if (document == null) { 180 throw new UnknownDocumentIdException( 181 "Document no longer exists. It may have been cancelled before being saved."); 182 } 183 184 WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); 185 if (!getDocumentDictionaryService().getDocumentAuthorizer(document).canOpen(document, 186 GlobalVariables.getUserSession().getPerson())) { 187 throw buildAuthorizationException("open", document); 188 } 189 190 // re-retrieve the document using the current user's session - remove 191 // the system user from the WorkflowDcument object 192 if (workflowDocument != document.getDocumentHeader().getWorkflowDocument()) { 193 LOG.warn("Workflow document changed via canOpen check"); 194 document.getDocumentHeader().setWorkflowDocument(workflowDocument); 195 } 196 197 form.setDocument(document); 198 form.setDocTypeName(workflowDocument.getDocumentTypeName()); 199 200 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument); 201 } 202 203 /** 204 * Creates a new document of the type specified by the docTypeName property of the given form. 205 * 206 * <p>This has been abstracted out so that it can be overridden in children if the need arises</p> 207 * 208 * @param form form instance that contains the doc type parameter and where 209 * the new document instance should be set 210 */ 211 protected void createDocument(DocumentFormBase form) throws WorkflowException { 212 if (LOG.isDebugEnabled()) { 213 LOG.debug("Creating new document instance for doc type: " + form.getDocTypeName()); 214 } 215 216 Document doc = getDocumentService().getNewDocument(form.getDocTypeName()); 217 218 form.setDocument(doc); 219 form.setDocTypeName(doc.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); 220 } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public ModelAndView cancel(UifFormBase form) { 227 performWorkflowAction((DocumentFormBase) form, UifConstants.WorkflowAction.CANCEL); 228 229 return getNavigationControllerService().returnToPrevious(form); 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public ModelAndView reload(DocumentFormBase form) throws WorkflowException { 237 Document document = form.getDocument(); 238 239 // prepare the reload action by calling dochandler (set doc id and command) 240 form.setDocId(document.getDocumentNumber()); 241 form.setCommand(DOCUMENT_LOAD_COMMANDS[1]); 242 243 form.setEvaluateFlagsAndModes(true); 244 form.setCanEditView(null); 245 246 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, RiceKeyConstants.MESSAGE_RELOADED); 247 248 return docHandler(form); 249 } 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public ModelAndView recall(DocumentFormBase form) { 256 performWorkflowAction(form, UifConstants.WorkflowAction.RECALL); 257 258 return getModelAndViewService().getModelAndView(form); 259 } 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override 265 public ModelAndView save(DocumentFormBase form) { 266 return save(form, null); 267 } 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override 273 public ModelAndView save(DocumentFormBase form, SaveDocumentEvent saveDocumentEvent) { 274 Document document = form.getDocument(); 275 276 // get the explanation from the document and check it for sensitive data 277 String explanation = document.getDocumentHeader().getExplanation(); 278 ModelAndView sensitiveDataDialogModelAndView = checkSensitiveDataAndWarningDialog(explanation, form); 279 280 // if a sensitive data warning dialog is returned then display it 281 if (sensitiveDataDialogModelAndView != null) { 282 return sensitiveDataDialogModelAndView; 283 } 284 285 performWorkflowAction(form, UifConstants.WorkflowAction.SAVE, saveDocumentEvent); 286 287 return getModelAndViewService().getModelAndView(form); 288 } 289 290 /** 291 * {@inheritDoc} 292 */ 293 @Override 294 public ModelAndView complete(DocumentFormBase form) { 295 performWorkflowAction(form, UifConstants.WorkflowAction.COMPLETE); 296 297 return getModelAndViewService().getModelAndView(form); 298 } 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override 304 public ModelAndView route(DocumentFormBase form) { 305 performWorkflowAction(form, UifConstants.WorkflowAction.ROUTE); 306 307 return getModelAndViewService().getModelAndView(form); 308 } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override 314 public ModelAndView blanketApprove(DocumentFormBase form) { 315 Document document = form.getDocument(); 316 WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); 317 318 //disable blanket approve if adhoc route completion exists 319 List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>(); 320 adHocRecipients.addAll(document.getAdHocRoutePersons()); 321 adHocRecipients.addAll(document.getAdHocRouteWorkgroups()); 322 323 //check for ad hoc completion request 324 for (AdHocRouteRecipient adHocRouteRecipient : adHocRecipients) { 325 String actionRequestedCode = adHocRouteRecipient.getActionRequested(); 326 327 //add error and send back 328 if (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestedCode)) { 329 GlobalVariables.getMessageMap().putError(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME, 330 RiceKeyConstants.ERROR_ADHOC_COMPLETE_BLANKET_APPROVE_NOT_ALLOWED); 331 332 return getModelAndViewService().getModelAndView(form); 333 } 334 } 335 336 performWorkflowAction(form, UifConstants.WorkflowAction.BLANKETAPPROVE); 337 338 if (GlobalVariables.getMessageMap().hasErrors()) { 339 return getModelAndViewService().getModelAndView(form); 340 } 341 342 return getNavigationControllerService().returnToPrevious(form); 343 } 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override 349 public ModelAndView approve(DocumentFormBase form) { 350 performWorkflowAction(form, UifConstants.WorkflowAction.APPROVE); 351 352 return getNavigationControllerService().returnToPrevious(form); 353 } 354 355 /** 356 * {@inheritDoc} 357 */ 358 @Override 359 public ModelAndView disapprove(DocumentFormBase form) { 360 // get the explanation for disapproval from the disapprove dialog and check it for sensitive data 361 String explanationData = generateDisapprovalNote(form); 362 ModelAndView sensitiveDataDialogModelAndView = checkSensitiveDataAndWarningDialog(explanationData, form); 363 364 // if a sensitive data warning dialog is returned then display it 365 if (sensitiveDataDialogModelAndView != null) { 366 return sensitiveDataDialogModelAndView; 367 } 368 369 performWorkflowAction(form, UifConstants.WorkflowAction.DISAPPROVE); 370 371 return getNavigationControllerService().returnToPrevious(form); 372 } 373 374 /** 375 * Convenience method for generating disapproval note with text from the explanation dialog. 376 * 377 * @param form document form instance containing the explanation dialog 378 * @return 379 */ 380 protected String generateDisapprovalNote(DocumentFormBase form) { 381 String explanationData = form.getDialogExplanations().get(EXPLANATION_DIALOG); 382 if(explanationData == null) { 383 return ""; 384 } 385 386 // build out full message to return 387 String introNoteMessage = getConfigurationService().getPropertyValueAsString( 388 RiceKeyConstants.MESSAGE_DISAPPROVAL_NOTE_TEXT_INTRO) + KRADConstants.BLANK_SPACE; 389 390 return introNoteMessage + explanationData; 391 } 392 393 /** 394 * {@inheritDoc} 395 */ 396 @Override 397 public ModelAndView fyi(DocumentFormBase form) { 398 performWorkflowAction(form, UifConstants.WorkflowAction.FYI); 399 400 return getNavigationControllerService().returnToPrevious(form); 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public ModelAndView acknowledge(DocumentFormBase form) { 408 performWorkflowAction(form, UifConstants.WorkflowAction.ACKNOWLEDGE); 409 410 return getNavigationControllerService().returnToPrevious(form); 411 } 412 413 /** 414 * {@inheritDoc} 415 */ 416 @Override 417 public ModelAndView sendAdHocRequests(DocumentFormBase form) { 418 performWorkflowAction(form, UifConstants.WorkflowAction.SENDADHOCREQUESTS); 419 420 return getModelAndViewService().getModelAndView(form); 421 } 422 423 /** 424 * {@inheritDoc} 425 */ 426 @Override 427 public ModelAndView supervisorFunctions(DocumentFormBase form) { 428 String workflowSuperUserUrl = getConfigurationService().getPropertyValueAsString(KRADConstants.WORKFLOW_URL_KEY) 429 + "/" 430 + KRADConstants.SUPERUSER_ACTION; 431 432 Properties props = new Properties(); 433 props.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.DISPLAY_SUPER_USER_DOCUMENT); 434 props.setProperty(UifPropertyPaths.DOCUMENT_ID, form.getDocument().getDocumentNumber()); 435 436 return getModelAndViewService().performRedirect(form, workflowSuperUserUrl, props); 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override 443 public ModelAndView close(DocumentFormBase form) { 444 return getNavigationControllerService().returnToPrevious(form); 445 } 446 447 /** 448 * Validates the note, saves attachment, adds the time stamp and author, and calls the 449 * generic addLine method. 450 * 451 * {@inheritDoc} 452 */ 453 @Override 454 public ModelAndView insertNote(DocumentFormBase form) { 455 Document document = form.getDocument(); 456 457 Note newNote = getAddLineNoteInstance(form); 458 setNewNoteProperties(form, document, newNote); 459 460 Attachment attachment = getNewNoteAttachment(form, document, newNote); 461 462 // validate the note 463 boolean rulesPassed = KRADServiceLocatorWeb.getKualiRuleService().applyRules(new AddNoteEvent(document, 464 newNote)); 465 if (!rulesPassed) { 466 return getModelAndViewService().getModelAndView(form); 467 } 468 469 // adding the attachment after refresh gets called, since the attachment record doesn't get persisted 470 // until the note does (and therefore refresh doesn't have any attachment to autoload based on the id, nor 471 // does it autopopulate the id since the note hasn't been persisted yet) 472 if (attachment != null) { 473 newNote.addAttachment(attachment); 474 } 475 476 // check for sensitive data within the note and display warning dialog if necessary 477 ModelAndView sensitiveDataDialogModelAndView = checkSensitiveDataAndWarningDialog(newNote.getNoteText(), form); 478 if (sensitiveDataDialogModelAndView != null) { 479 return sensitiveDataDialogModelAndView; 480 } 481 482 saveNewNote(form, document, newNote); 483 484 return getCollectionControllerService().addLine(form); 485 } 486 487 /** 488 * Retrieves the note instance on the form that should be added to the document notes. 489 * 490 * @param form form instance containing the add note instance 491 * @return 492 */ 493 protected Note getAddLineNoteInstance(DocumentFormBase form) { 494 String selectedCollectionId = form.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_ID); 495 496 BindingInfo addLineBindingInfo = (BindingInfo) form.getViewPostMetadata().getComponentPostData( 497 selectedCollectionId, UifConstants.PostMetadata.ADD_LINE_BINDING_INFO); 498 499 String addLinePath = addLineBindingInfo.getBindingPath(); 500 Object addLine = ObjectPropertyUtils.getPropertyValue(form, addLinePath); 501 502 return (Note) addLine; 503 } 504 505 /** 506 * Defaults properties (posted timestamp, object id, author) on the note instance that will be added. 507 * 508 * @param form form instance containing the add note instance 509 * @param document document instance the note will be added to 510 * @param newNote note instance to set properties on 511 */ 512 protected void setNewNoteProperties(DocumentFormBase form, Document document, Note newNote) { 513 newNote.setNotePostedTimestampToCurrent(); 514 newNote.setRemoteObjectIdentifier(document.getNoteTarget().getObjectId()); 515 516 Person kualiUser = GlobalVariables.getUserSession().getPerson(); 517 if (kualiUser == null) { 518 throw new IllegalStateException("Current UserSession has a null Person."); 519 } 520 521 newNote.setAuthorUniversalIdentifier(kualiUser.getPrincipalId()); 522 } 523 524 /** 525 * Builds an attachment for the file (if any) associated with the add note instance. 526 * 527 * @param form form instance containing the attachment file 528 * @param document document instance the attachment should be associated with 529 * @param newNote note instance the attachment should be associated with 530 * @return Attachment instance for the note, or null if no attachment file was present 531 */ 532 protected Attachment getNewNoteAttachment(DocumentFormBase form, Document document, Note newNote) { 533 MultipartFile attachmentFile = form.getAttachmentFile(); 534 535 if ((attachmentFile == null) || StringUtils.isBlank(attachmentFile.getOriginalFilename())) { 536 return null; 537 } 538 539 if (attachmentFile.getSize() == 0) { 540 GlobalVariables.getMessageMap().putError(String.format("%s.%s", 541 KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME, KRADConstants.NOTE_ATTACHMENT_FILE_PROPERTY_NAME), 542 RiceKeyConstants.ERROR_UPLOADFILE_EMPTY, attachmentFile.getOriginalFilename()); 543 544 return null; 545 } 546 547 String attachmentTypeCode = null; 548 if (newNote.getAttachment() != null) { 549 attachmentTypeCode = newNote.getAttachment().getAttachmentTypeCode(); 550 } 551 552 DocumentAuthorizer documentAuthorizer = getDocumentDictionaryService().getDocumentAuthorizer(document); 553 if (!documentAuthorizer.canAddNoteAttachment(document, attachmentTypeCode, 554 GlobalVariables.getUserSession().getPerson())) { 555 throw buildAuthorizationException("annotate", document); 556 } 557 558 Attachment attachment; 559 try { 560 attachment = getAttachmentService().createAttachment(document.getNoteTarget(), 561 attachmentFile.getOriginalFilename(), attachmentFile.getContentType(), 562 (int) attachmentFile.getSize(), attachmentFile.getInputStream(), attachmentTypeCode); 563 } catch (IOException e) { 564 throw new RiceRuntimeException("Unable to store attachment", e); 565 } 566 567 return attachment; 568 } 569 570 /** 571 * Saves a new note instance to the data store if the document state allows it. 572 * 573 * @param form form instance containing the add note instance 574 * @param document document instance the note is associated with 575 * @param newNote note instance to save 576 */ 577 protected void saveNewNote(DocumentFormBase form, Document document, Note newNote) { 578 DocumentHeader documentHeader = document.getDocumentHeader(); 579 580 if (!documentHeader.getWorkflowDocument().isInitiated() && StringUtils.isNotEmpty( 581 document.getNoteTarget().getObjectId()) && !(document instanceof MaintenanceDocument && NoteType 582 .BUSINESS_OBJECT.getCode().equals(newNote.getNoteTypeCode()))) { 583 584 getNoteService().save(newNote); 585 } 586 } 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override 592 public ModelAndView deleteNote(DocumentFormBase form) { 593 String selectedLineIndex = form.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX); 594 595 Document document = form.getDocument(); 596 597 Note note = document.getNote(Integer.parseInt(selectedLineIndex)); 598 Attachment attachment = note.getAttachment(); 599 600 String attachmentTypeCode = null; 601 if (attachment != null) { 602 attachmentTypeCode = attachment.getAttachmentTypeCode(); 603 } 604 605 // verify the user has permissions to delete the note 606 Person user = GlobalVariables.getUserSession().getPerson(); 607 if (!getDocumentDictionaryService().getDocumentAuthorizer(document).canDeleteNoteAttachment(document, 608 attachmentTypeCode, note.getAuthorUniversalIdentifier(), user)) { 609 throw buildAuthorizationException("annotate", document); 610 } 611 612 if (attachment != null && attachment.isComplete()) { 613 getAttachmentService().deleteAttachmentContents(attachment); 614 } 615 616 // if document is not saved there is no need to delete the note (it is not persisted) 617 if (!document.getDocumentHeader().getWorkflowDocument().isInitiated()) { 618 getNoteService().deleteNote(note); 619 } 620 621 return getCollectionControllerService().deleteLine(form); 622 } 623 624 /** 625 * Retrieves a note attachment by either the line index of the note within the documents note collection, or 626 * by the note identifier. 627 * 628 * {@inheritDoc} 629 */ 630 @Override 631 public ModelAndView downloadAttachment(DocumentFormBase form, HttpServletResponse response) { 632 Attachment attachment = null; 633 634 String selectedLineIndex = form.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX); 635 if (StringUtils.isNotBlank(selectedLineIndex)) { 636 Note note = form.getDocument().getNote(Integer.parseInt(selectedLineIndex)); 637 attachment = note.getAttachment(); 638 } else { 639 Long noteIdentifier = Long.valueOf(form.getActionParamaterValue(KRADConstants.NOTE_IDENTIFIER)); 640 Note note = getNoteService().getNoteByNoteId(noteIdentifier); 641 if ((note != null) && (note.getAttachment() != null)) { 642 attachment = note.getAttachment(); 643 644 // make sure the reference back to note is set for the note service dependencies 645 attachment.setNote(note); 646 } 647 } 648 649 if (attachment == null) { 650 throw new RuntimeException("Unable to find attachment for action parameters passed."); 651 } 652 653 try { 654 KRADUtils.addAttachmentToResponse(response, getAttachmentService().retrieveAttachmentContents(attachment), 655 attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentFileName(), 656 attachment.getAttachmentFileSize().longValue()); 657 } catch (IOException e) { 658 throw new RuntimeException("Unable to download note attachment", e); 659 } 660 661 return null; 662 } 663 664 /** 665 * {@inheritDoc} 666 */ 667 @Override 668 public ModelAndView cancelAttachment(DocumentFormBase form) { 669 form.setAttachmentFile(null); 670 671 return getModelAndViewService().getModelAndView(form); 672 } 673 674 /** 675 * {@inheritDoc} 676 */ 677 @Override 678 public ModelAndView superUserTakeActions(DocumentFormBase form) { 679 Document document = form.getDocument(); 680 681 if (StringUtils.isBlank(document.getSuperUserAnnotation())) { 682 GlobalVariables.getMessageMap().putErrorForSectionId( 683 "Uif-SuperUserAnnotation", RiceKeyConstants.ERROR_SUPER_USER_TAKE_ACTIONS_MISSING); 684 } 685 686 Set<String> selectedActionRequests = form.getSelectedCollectionLines().get(UifPropertyPaths.ACTION_REQUESTS); 687 688 if (CollectionUtils.isEmpty(selectedActionRequests)) { 689 GlobalVariables.getMessageMap().putErrorForSectionId( 690 "Uif-SuperUserActionRequests", RiceKeyConstants.ERROR_SUPER_USER_TAKE_ACTIONS_NONE_SELECTED); 691 } 692 693 if (GlobalVariables.getMessageMap().hasErrors()) { 694 return getModelAndViewService().getModelAndView(form); 695 } 696 697 List<ActionRequest> actionRequests = new ArrayList<ActionRequest>(); 698 for (String selectedActionRequest : selectedActionRequests) { 699 ActionRequest actionRequest = ObjectPropertyUtils.getPropertyValue(document, selectedActionRequest); 700 actionRequests.add(actionRequest); 701 } 702 703 for (ActionRequest actionRequest : actionRequests) { 704 if (StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.COMPLETE.getCode()) || 705 StringUtils.equals(actionRequest.getActionRequested().getCode(), ActionRequestType.APPROVE.getCode())) { 706 document = getDocumentService().validateAndPersistDocument(document, new RouteDocumentEvent(document)); 707 form.setDocument(document); 708 } 709 710 performSuperUserWorkflowAction(form, UifConstants.SuperUserWorkflowAction.TAKEACTION, actionRequest); 711 } 712 713 document.setSuperUserAnnotation(""); 714 form.getSelectedCollectionLines().remove(UifPropertyPaths.ACTION_REQUESTS); 715 716 return getModelAndViewService().getModelAndView(form); 717 } 718 719 /** 720 * {@inheritDoc} 721 */ 722 @Override 723 public ModelAndView superUserApprove(DocumentFormBase form) { 724 Document document = form.getDocument(); 725 726 if (StringUtils.isBlank(document.getSuperUserAnnotation())) { 727 GlobalVariables.getMessageMap().putErrorForSectionId( 728 "Uif-SuperUserAnnotation", RiceKeyConstants.ERROR_SUPER_USER_APPROVE_MISSING); 729 } 730 731 Set<String> selectedCollectionLines = form.getSelectedCollectionLines().get(UifPropertyPaths.ACTION_REQUESTS); 732 733 if (!CollectionUtils.isEmpty(selectedCollectionLines)) { 734 GlobalVariables.getMessageMap().putErrorForSectionId( 735 "Uif-SuperUserActionRequests", RiceKeyConstants.ERROR_SUPER_USER_APPROVE_ACTIONS_CHECKED); 736 } 737 738 if (GlobalVariables.getMessageMap().hasErrors()) { 739 return getModelAndViewService().getModelAndView(form); 740 } 741 742 performSuperUserWorkflowAction(form, UifConstants.SuperUserWorkflowAction.APPROVE); 743 744 return getModelAndViewService().getModelAndView(form); 745 } 746 747 /** 748 * {@inheritDoc} 749 */ 750 @Override 751 public ModelAndView superUserDisapprove(DocumentFormBase form) { 752 Document document = form.getDocument(); 753 754 if (StringUtils.isBlank(document.getSuperUserAnnotation())) { 755 GlobalVariables.getMessageMap().putErrorForSectionId( 756 "Uif-SuperUserAnnotation", RiceKeyConstants.ERROR_SUPER_USER_DISAPPROVE_MISSING); 757 } 758 759 Set<String> selectedCollectionLines = form.getSelectedCollectionLines().get(UifPropertyPaths.ACTION_REQUESTS); 760 761 if (!CollectionUtils.isEmpty(selectedCollectionLines)) { 762 GlobalVariables.getMessageMap().putErrorForSectionId( 763 "Uif-SuperUserActionRequests", RiceKeyConstants.ERROR_SUPER_USER_DISAPPROVE_ACTIONS_CHECKED); 764 } 765 766 if (GlobalVariables.getMessageMap().hasErrors()) { 767 return getModelAndViewService().getModelAndView(form); 768 } 769 770 performSuperUserWorkflowAction(form, UifConstants.SuperUserWorkflowAction.DISAPPROVE); 771 772 return getModelAndViewService().getModelAndView(form); 773 } 774 775 /** 776 * {@inheritDoc} 777 */ 778 @Override 779 public void performWorkflowAction(DocumentFormBase form, UifConstants.WorkflowAction action) { 780 performWorkflowAction(form, action, null); 781 } 782 783 /** 784 * {@inheritDoc} 785 */ 786 @Override 787 public void performWorkflowAction(DocumentFormBase form, UifConstants.WorkflowAction action, 788 DocumentEvent documentEvent) { 789 Document document = form.getDocument(); 790 791 if (LOG.isDebugEnabled()) { 792 LOG.debug("Performing workflow action " + action.name() + "for document: " + document.getDocumentNumber()); 793 } 794 795 // evaluate flags on save only if we are saving for the first time (transitioning to saved status) 796 if (!UifConstants.WorkflowAction.SAVE.equals(action) || document.getDocumentHeader().getWorkflowDocument() 797 .isInitiated()) { 798 form.setEvaluateFlagsAndModes(true); 799 form.setCanEditView(null); 800 } 801 802 try { 803 String successMessageKey = null; 804 switch (action) { 805 case SAVE: 806 if (documentEvent == null) { 807 document = getDocumentService().saveDocument(document); 808 } else { 809 document = getDocumentService().saveDocument(document, documentEvent); 810 } 811 812 successMessageKey = RiceKeyConstants.MESSAGE_SAVED; 813 break; 814 case ROUTE: 815 document = getDocumentService().routeDocument(document, form.getAnnotation(), 816 combineAdHocRecipients(form)); 817 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL; 818 break; 819 case BLANKETAPPROVE: 820 document = getDocumentService().blanketApproveDocument(document, form.getAnnotation(), 821 combineAdHocRecipients(form)); 822 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_APPROVED; 823 break; 824 case APPROVE: 825 document = getDocumentService().approveDocument(document, form.getAnnotation(), 826 combineAdHocRecipients(form)); 827 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_APPROVED; 828 break; 829 case DISAPPROVE: 830 String disapprovalNoteText = generateDisapprovalNote(form); 831 document = getDocumentService().disapproveDocument(document, disapprovalNoteText); 832 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_DISAPPROVED; 833 break; 834 case FYI: 835 document = getDocumentService().clearDocumentFyi(document, combineAdHocRecipients(form)); 836 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_FYIED; 837 break; 838 case ACKNOWLEDGE: 839 document = getDocumentService().acknowledgeDocument(document, form.getAnnotation(), 840 combineAdHocRecipients(form)); 841 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_ACKNOWLEDGED; 842 break; 843 case CANCEL: 844 if (getDocumentService().documentExists(document.getDocumentNumber())) { 845 document = getDocumentService().cancelDocument(document, form.getAnnotation()); 846 successMessageKey = RiceKeyConstants.MESSAGE_CANCELLED; 847 } 848 break; 849 case COMPLETE: 850 if (getDocumentService().documentExists(document.getDocumentNumber())) { 851 document = getDocumentService().completeDocument(document, form.getAnnotation(), 852 combineAdHocRecipients(form)); 853 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL; 854 } 855 break; 856 case SENDADHOCREQUESTS: 857 getDocumentService().sendAdHocRequests(document, form.getAnnotation(), combineAdHocRecipients( 858 form)); 859 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL; 860 break; 861 case RECALL: 862 if (getDocumentService().documentExists(document.getDocumentNumber())) { 863 String recallExplanation = form.getDialogExplanations().get( 864 KRADConstants.QUESTION_ACTION_RECALL_REASON); 865 document = getDocumentService().recallDocument(document, recallExplanation, true); 866 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_RECALLED; 867 } 868 break; 869 } 870 871 // push potentially updated document back into the form 872 form.setDocument(document); 873 874 if (successMessageKey != null) { 875 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey); 876 } 877 } catch (ValidationException e) { 878 // log the error and swallow exception so screen will draw with errors. 879 // we don't want the exception to bubble up and the user to see an incident page, but instead just return to 880 // the page and display the actual errors. This would need a fix to the API at some point. 881 KRADUtils.logErrors(); 882 LOG.error("Validation Exception occured for document :" + document.getDocumentNumber(), e); 883 884 // if no errors in map then throw runtime because something bad happened 885 if (GlobalVariables.getMessageMap().hasNoErrors()) { 886 throw new RiceRuntimeException("Validation Exception with no error message.", e); 887 } 888 } catch (Exception e) { 889 throw new RiceRuntimeException( 890 "Exception trying to invoke action " + action.name() + " for document: " + document 891 .getDocumentNumber(), e); 892 } 893 894 form.setAnnotation(""); 895 } 896 897 /** 898 * Convenience method to combine the two lists of ad hoc recipients into one which should be done before 899 * calling any of the document service methods that expect a list of ad hoc recipients. 900 * 901 * @param form document form instance containing the ad hod lists 902 * @return List<AdHocRouteRecipient> combined ad hoc recipients 903 */ 904 protected List<AdHocRouteRecipient> combineAdHocRecipients(DocumentFormBase form) { 905 Document document = form.getDocument(); 906 907 List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>(); 908 adHocRecipients.addAll(document.getAdHocRoutePersons()); 909 adHocRecipients.addAll(document.getAdHocRouteWorkgroups()); 910 911 return adHocRecipients; 912 } 913 914 /** 915 * {@inheritDoc} 916 */ 917 @Override 918 public void performSuperUserWorkflowAction(DocumentFormBase form, UifConstants.SuperUserWorkflowAction action) { 919 performSuperUserWorkflowAction(form, action, null); 920 } 921 922 /** 923 * {@inheritDoc} 924 */ 925 @Override 926 public void performSuperUserWorkflowAction(DocumentFormBase form, UifConstants.SuperUserWorkflowAction action, 927 ActionRequest actionRequest) { 928 Document document = form.getDocument(); 929 930 if (LOG.isDebugEnabled()) { 931 LOG.debug("Performing super user workflow action " + action.name() + "for document: " + document.getDocumentNumber()); 932 } 933 934 try { 935 String documentTypeId = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeId(); 936 String documentNumber = document.getDocumentNumber(); 937 String principalId = GlobalVariables.getUserSession().getPrincipalId(); 938 String superUserAnnotation = document.getSuperUserAnnotation(); 939 940 WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentTypeId); 941 DocumentActionParameters parameters = DocumentActionParameters.create(documentNumber, principalId, superUserAnnotation); 942 943 String successMessageKey = null; 944 switch (action) { 945 case TAKEACTION: 946 if (actionRequest != null) { 947 documentActions.superUserTakeRequestedAction(parameters, true, actionRequest.getId()); 948 949 String actionRequestedCode = actionRequest.getActionRequested().getCode(); 950 if (StringUtils.equals(actionRequestedCode, ActionRequestType.ACKNOWLEDGE.getCode())) { 951 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_ACKNOWLEDGED; 952 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.FYI.getCode())) { 953 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_FYIED; 954 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.COMPLETE.getCode())) { 955 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_COMPLETED; 956 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.APPROVE.getCode())) { 957 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_APPROVED; 958 } else { 959 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_APPROVED; 960 } 961 } 962 break; 963 case APPROVE: 964 documentActions.superUserBlanketApprove(parameters, true); 965 966 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_APPROVED; 967 break; 968 case DISAPPROVE: 969 documentActions.superUserDisapprove(parameters, true); 970 971 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_DISAPPROVED; 972 break; 973 } 974 975 form.setEvaluateFlagsAndModes(true); 976 form.setCanEditView(null); 977 978 if (successMessageKey != null) { 979 if (actionRequest != null) { 980 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey, 981 document.getDocumentNumber(), actionRequest.getId()); 982 } else { 983 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey, 984 document.getDocumentNumber()); 985 } 986 } 987 } catch (ValidationException e) { 988 // log the error and swallow exception so screen will draw with errors. 989 // we don't want the exception to bubble up and the user to see an incident page, but instead just return to 990 // the page and display the actual errors. This would need a fix to the API at some point. 991 KRADUtils.logErrors(); 992 LOG.error("Validation Exception occured for document :" + document.getDocumentNumber(), e); 993 994 // if no errors in map then throw runtime because something bad happened 995 if (GlobalVariables.getMessageMap().hasNoErrors()) { 996 throw new RiceRuntimeException("Validation Exception with no error message.", e); 997 } 998 } catch (Exception e) { 999 throw new RiceRuntimeException( 1000 "Exception trying to invoke action " + action.name() + " for document: " + document 1001 .getDocumentNumber(), e); 1002 } 1003 1004 document.setSuperUserAnnotation(""); 1005 } 1006 1007 /** 1008 * Helper method to get the correct {@link WorkflowDocumentActionsService} from the {@code applicationId} of the 1009 * document type. 1010 * 1011 * @param documentTypeId the document type to get the application id from 1012 * 1013 * @return the correct {@link WorkflowDocumentActionsService} from the {@code applicationId} of the document type 1014 */ 1015 protected WorkflowDocumentActionsService getWorkflowDocumentActionsService(String documentTypeId) { 1016 DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(documentTypeId); 1017 String applicationId = documentType.getApplicationId(); 1018 QName serviceName = new QName(KewApiConstants.Namespaces.KEW_NAMESPACE_2_0, 1019 KewApiConstants.ServiceNames.WORKFLOW_DOCUMENT_ACTIONS_SERVICE_SOAP); 1020 1021 WorkflowDocumentActionsService service = (WorkflowDocumentActionsService) KsbApiServiceLocator.getServiceBus() 1022 .getService(serviceName, applicationId); 1023 1024 if (service == null) { 1025 service = KewApiServiceLocator.getWorkflowDocumentActionsService(); 1026 } 1027 1028 return service; 1029 } 1030 1031 /** 1032 * Helper method to check if sensitive data is present in a given string and dialog display. 1033 * 1034 * <p>If the string is sensitive we want to return a dialog box to make sure user wants to continue, 1035 * else we just return null</p> 1036 * 1037 * @param field the string to check for sensitive data 1038 * @param form the form to add the dialog to 1039 * @return the model and view for the dialog or null if there isn't one 1040 */ 1041 protected ModelAndView checkSensitiveDataAndWarningDialog(String field, UifFormBase form) { 1042 boolean hasSensitiveData = KRADUtils.containsSensitiveDataPatternMatch(field); 1043 Boolean warnForSensitiveData = getParameterService().getParameterValueAsBoolean(KRADConstants.KNS_NAMESPACE, 1044 ParameterConstants.ALL_COMPONENT, 1045 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND); 1046 1047 // if there is sensitive data and the flag to warn for sensitive data is set, 1048 // then we want a dialog returned if there is not already one 1049 if (hasSensitiveData && warnForSensitiveData.booleanValue()) { 1050 DialogResponse sensitiveDataDialogResponse = form.getDialogResponse(SENSITIVE_DATA_DIALOG); 1051 1052 if (sensitiveDataDialogResponse == null) { 1053 // no sensitive data dialog found, so create one on the form and return it 1054 return getModelAndViewService().showDialog(SENSITIVE_DATA_DIALOG, true, form); 1055 } 1056 } 1057 1058 return null; 1059 } 1060 1061 /** 1062 * Convenience method for building document authorization exceptions. 1063 * 1064 * @param action the action that was requested 1065 * @param document document instance the action was requested for 1066 */ 1067 protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) { 1068 return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), 1069 action, document.getDocumentNumber()); 1070 } 1071 1072 protected LegacyDataAdapter getLegacyDataAdapter() { 1073 if (this.legacyDataAdapter == null) { 1074 this.legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter(); 1075 } 1076 return this.legacyDataAdapter; 1077 } 1078 1079 public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) { 1080 this.legacyDataAdapter = legacyDataAdapter; 1081 } 1082 1083 protected DataDictionaryService getDataDictionaryService() { 1084 if (this.dataDictionaryService == null) { 1085 this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 1086 } 1087 return this.dataDictionaryService; 1088 } 1089 1090 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 1091 this.dataDictionaryService = dataDictionaryService; 1092 } 1093 1094 protected DocumentService getDocumentService() { 1095 if (this.documentService == null) { 1096 this.documentService = KRADServiceLocatorWeb.getDocumentService(); 1097 } 1098 return this.documentService; 1099 } 1100 1101 public void setDocumentService(DocumentService documentService) { 1102 this.documentService = documentService; 1103 } 1104 1105 protected DocumentDictionaryService getDocumentDictionaryService() { 1106 if (this.documentDictionaryService == null) { 1107 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1108 } 1109 return this.documentDictionaryService; 1110 } 1111 1112 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 1113 this.documentDictionaryService = documentDictionaryService; 1114 } 1115 1116 protected AttachmentService getAttachmentService() { 1117 if (attachmentService == null) { 1118 attachmentService = KRADServiceLocator.getAttachmentService(); 1119 } 1120 return this.attachmentService; 1121 } 1122 1123 public void setAttachmentService(AttachmentService attachmentService) { 1124 this.attachmentService = attachmentService; 1125 } 1126 1127 protected NoteService getNoteService() { 1128 if (noteService == null) { 1129 noteService = KRADServiceLocator.getNoteService(); 1130 } 1131 1132 return this.noteService; 1133 } 1134 1135 public void setNoteService(NoteService noteService) { 1136 this.noteService = noteService; 1137 } 1138 1139 protected ModelAndViewService getModelAndViewService() { 1140 return modelAndViewService; 1141 } 1142 1143 public void setModelAndViewService(ModelAndViewService modelAndViewService) { 1144 this.modelAndViewService = modelAndViewService; 1145 } 1146 1147 protected NavigationControllerService getNavigationControllerService() { 1148 return navigationControllerService; 1149 } 1150 1151 public void setNavigationControllerService(NavigationControllerService navigationControllerService) { 1152 this.navigationControllerService = navigationControllerService; 1153 } 1154 1155 protected ConfigurationService getConfigurationService() { 1156 return configurationService; 1157 } 1158 1159 public void setConfigurationService(ConfigurationService configurationService) { 1160 this.configurationService = configurationService; 1161 } 1162 1163 protected CollectionControllerService getCollectionControllerService() { 1164 return collectionControllerService; 1165 } 1166 1167 public void setCollectionControllerService(CollectionControllerService collectionControllerService) { 1168 this.collectionControllerService = collectionControllerService; 1169 } 1170 1171 protected ParameterService getParameterService() { 1172 return parameterService; 1173 } 1174 1175 public void setParameterService(ParameterService parameterService) { 1176 this.parameterService = parameterService; 1177 } 1178}