001/** 002 * Copyright 2005-2018 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(form)); 858 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL; 859 break; 860 case RECALL: 861 if (getDocumentService().documentExists(document.getDocumentNumber())) { 862 String recallExplanation = form.getDialogExplanations().get( 863 KRADConstants.QUESTION_ACTION_RECALL_REASON); 864 document = getDocumentService().recallDocument(document, recallExplanation, true); 865 successMessageKey = RiceKeyConstants.MESSAGE_ROUTE_RECALLED; 866 } 867 break; 868 } 869 870 // push potentially updated document back into the form 871 form.setDocument(document); 872 873 if (successMessageKey != null) { 874 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey); 875 } 876 } catch (ValidationException e) { 877 // log the error and swallow exception so screen will draw with errors. 878 // we don't want the exception to bubble up and the user to see an incident page, but instead just return to 879 // the page and display the actual errors. This would need a fix to the API at some point. 880 KRADUtils.logErrors(); 881 LOG.error("Validation Exception occured for document :" + document.getDocumentNumber(), e); 882 883 // if no errors in map then throw runtime because something bad happened 884 if (GlobalVariables.getMessageMap().hasNoErrors()) { 885 throw new RiceRuntimeException("Validation Exception with no error message.", e); 886 } 887 } catch (Exception e) { 888 throw new RiceRuntimeException( 889 "Exception trying to invoke action " + action.name() + " for document: " + document 890 .getDocumentNumber(), e); 891 } 892 893 form.setAnnotation(""); 894 } 895 896 /** 897 * Convenience method to combine the two lists of ad hoc recipients into one which should be done before 898 * calling any of the document service methods that expect a list of ad hoc recipients. 899 * 900 * @param form document form instance containing the ad hod lists 901 * @return List<AdHocRouteRecipient> combined ad hoc recipients 902 */ 903 protected List<AdHocRouteRecipient> combineAdHocRecipients(DocumentFormBase form) { 904 Document document = form.getDocument(); 905 906 List<AdHocRouteRecipient> adHocRecipients = new ArrayList<AdHocRouteRecipient>(); 907 adHocRecipients.addAll(document.getAdHocRoutePersons()); 908 adHocRecipients.addAll(document.getAdHocRouteWorkgroups()); 909 910 return adHocRecipients; 911 } 912 913 /** 914 * {@inheritDoc} 915 */ 916 @Override 917 public void performSuperUserWorkflowAction(DocumentFormBase form, UifConstants.SuperUserWorkflowAction action) { 918 performSuperUserWorkflowAction(form, action, null); 919 } 920 921 /** 922 * {@inheritDoc} 923 */ 924 @Override 925 public void performSuperUserWorkflowAction(DocumentFormBase form, UifConstants.SuperUserWorkflowAction action, 926 ActionRequest actionRequest) { 927 Document document = form.getDocument(); 928 929 if (LOG.isDebugEnabled()) { 930 LOG.debug("Performing super user workflow action " + action.name() + "for document: " + document.getDocumentNumber()); 931 } 932 933 try { 934 String documentTypeId = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeId(); 935 String documentNumber = document.getDocumentNumber(); 936 String principalId = GlobalVariables.getUserSession().getPrincipalId(); 937 String superUserAnnotation = document.getSuperUserAnnotation(); 938 939 WorkflowDocumentActionsService documentActions = getWorkflowDocumentActionsService(documentTypeId); 940 DocumentActionParameters parameters = DocumentActionParameters.create(documentNumber, principalId, superUserAnnotation); 941 942 String successMessageKey = null; 943 switch (action) { 944 case TAKEACTION: 945 if (actionRequest != null) { 946 documentActions.superUserTakeRequestedAction(parameters, true, actionRequest.getId()); 947 948 String actionRequestedCode = actionRequest.getActionRequested().getCode(); 949 if (StringUtils.equals(actionRequestedCode, ActionRequestType.ACKNOWLEDGE.getCode())) { 950 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_ACKNOWLEDGED; 951 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.FYI.getCode())) { 952 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_FYIED; 953 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.COMPLETE.getCode())) { 954 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_COMPLETED; 955 } else if (StringUtils.equals(actionRequestedCode, ActionRequestType.APPROVE.getCode())) { 956 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_APPROVED; 957 } else { 958 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_ACTION_REQUEST_APPROVED; 959 } 960 } 961 break; 962 case APPROVE: 963 documentActions.superUserBlanketApprove(parameters, true); 964 965 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_APPROVED; 966 break; 967 case DISAPPROVE: 968 documentActions.superUserDisapprove(parameters, true); 969 970 successMessageKey = RiceKeyConstants.MESSAGE_SUPER_USER_DISAPPROVED; 971 break; 972 } 973 974 form.setEvaluateFlagsAndModes(true); 975 form.setCanEditView(null); 976 977 if (successMessageKey != null) { 978 if (actionRequest != null) { 979 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey, 980 document.getDocumentNumber(), actionRequest.getId()); 981 } else { 982 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_MESSAGES, successMessageKey, 983 document.getDocumentNumber()); 984 } 985 } 986 } catch (ValidationException e) { 987 // log the error and swallow exception so screen will draw with errors. 988 // we don't want the exception to bubble up and the user to see an incident page, but instead just return to 989 // the page and display the actual errors. This would need a fix to the API at some point. 990 KRADUtils.logErrors(); 991 LOG.error("Validation Exception occured for document :" + document.getDocumentNumber(), e); 992 993 // if no errors in map then throw runtime because something bad happened 994 if (GlobalVariables.getMessageMap().hasNoErrors()) { 995 throw new RiceRuntimeException("Validation Exception with no error message.", e); 996 } 997 } catch (Exception e) { 998 throw new RiceRuntimeException( 999 "Exception trying to invoke action " + action.name() + " for document: " + document 1000 .getDocumentNumber(), e); 1001 } 1002 1003 document.setSuperUserAnnotation(""); 1004 } 1005 1006 /** 1007 * Helper method to get the correct {@link WorkflowDocumentActionsService} from the {@code applicationId} of the 1008 * document type. 1009 * 1010 * @param documentTypeId the document type to get the application id from 1011 * 1012 * @return the correct {@link WorkflowDocumentActionsService} from the {@code applicationId} of the document type 1013 */ 1014 protected WorkflowDocumentActionsService getWorkflowDocumentActionsService(String documentTypeId) { 1015 DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeById(documentTypeId); 1016 String applicationId = documentType.getApplicationId(); 1017 QName serviceName = new QName(KewApiConstants.Namespaces.KEW_NAMESPACE_2_0, 1018 KewApiConstants.ServiceNames.WORKFLOW_DOCUMENT_ACTIONS_SERVICE_SOAP); 1019 1020 WorkflowDocumentActionsService service = (WorkflowDocumentActionsService) KsbApiServiceLocator.getServiceBus() 1021 .getService(serviceName, applicationId); 1022 1023 if (service == null) { 1024 service = KewApiServiceLocator.getWorkflowDocumentActionsService(); 1025 } 1026 1027 return service; 1028 } 1029 1030 /** 1031 * Helper method to check if sensitive data is present in a given string and dialog display. 1032 * 1033 * <p>If the string is sensitive we want to return a dialog box to make sure user wants to continue, 1034 * else we just return null</p> 1035 * 1036 * @param field the string to check for sensitive data 1037 * @param form the form to add the dialog to 1038 * @return the model and view for the dialog or null if there isn't one 1039 */ 1040 protected ModelAndView checkSensitiveDataAndWarningDialog(String field, UifFormBase form) { 1041 boolean hasSensitiveData = KRADUtils.containsSensitiveDataPatternMatch(field); 1042 Boolean warnForSensitiveData = getParameterService().getParameterValueAsBoolean(KRADConstants.KNS_NAMESPACE, 1043 ParameterConstants.ALL_COMPONENT, 1044 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND); 1045 1046 // if there is sensitive data and the flag to warn for sensitive data is set, 1047 // then we want a dialog returned if there is not already one 1048 if (hasSensitiveData && warnForSensitiveData.booleanValue()) { 1049 DialogResponse sensitiveDataDialogResponse = form.getDialogResponse(SENSITIVE_DATA_DIALOG); 1050 1051 if (sensitiveDataDialogResponse == null) { 1052 // no sensitive data dialog found, so create one on the form and return it 1053 return getModelAndViewService().showDialog(SENSITIVE_DATA_DIALOG, true, form); 1054 } 1055 } 1056 1057 return null; 1058 } 1059 1060 /** 1061 * Convenience method for building document authorization exceptions. 1062 * 1063 * @param action the action that was requested 1064 * @param document document instance the action was requested for 1065 */ 1066 protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) { 1067 return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), 1068 action, document.getDocumentNumber()); 1069 } 1070 1071 protected LegacyDataAdapter getLegacyDataAdapter() { 1072 if (this.legacyDataAdapter == null) { 1073 this.legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter(); 1074 } 1075 return this.legacyDataAdapter; 1076 } 1077 1078 public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) { 1079 this.legacyDataAdapter = legacyDataAdapter; 1080 } 1081 1082 protected DataDictionaryService getDataDictionaryService() { 1083 if (this.dataDictionaryService == null) { 1084 this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 1085 } 1086 return this.dataDictionaryService; 1087 } 1088 1089 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 1090 this.dataDictionaryService = dataDictionaryService; 1091 } 1092 1093 protected DocumentService getDocumentService() { 1094 if (this.documentService == null) { 1095 this.documentService = KRADServiceLocatorWeb.getDocumentService(); 1096 } 1097 return this.documentService; 1098 } 1099 1100 public void setDocumentService(DocumentService documentService) { 1101 this.documentService = documentService; 1102 } 1103 1104 protected DocumentDictionaryService getDocumentDictionaryService() { 1105 if (this.documentDictionaryService == null) { 1106 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1107 } 1108 return this.documentDictionaryService; 1109 } 1110 1111 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 1112 this.documentDictionaryService = documentDictionaryService; 1113 } 1114 1115 protected AttachmentService getAttachmentService() { 1116 if (attachmentService == null) { 1117 attachmentService = KRADServiceLocator.getAttachmentService(); 1118 } 1119 return this.attachmentService; 1120 } 1121 1122 public void setAttachmentService(AttachmentService attachmentService) { 1123 this.attachmentService = attachmentService; 1124 } 1125 1126 protected NoteService getNoteService() { 1127 if (noteService == null) { 1128 noteService = KRADServiceLocator.getNoteService(); 1129 } 1130 1131 return this.noteService; 1132 } 1133 1134 public void setNoteService(NoteService noteService) { 1135 this.noteService = noteService; 1136 } 1137 1138 protected ModelAndViewService getModelAndViewService() { 1139 return modelAndViewService; 1140 } 1141 1142 public void setModelAndViewService(ModelAndViewService modelAndViewService) { 1143 this.modelAndViewService = modelAndViewService; 1144 } 1145 1146 protected NavigationControllerService getNavigationControllerService() { 1147 return navigationControllerService; 1148 } 1149 1150 public void setNavigationControllerService(NavigationControllerService navigationControllerService) { 1151 this.navigationControllerService = navigationControllerService; 1152 } 1153 1154 protected ConfigurationService getConfigurationService() { 1155 return configurationService; 1156 } 1157 1158 public void setConfigurationService(ConfigurationService configurationService) { 1159 this.configurationService = configurationService; 1160 } 1161 1162 protected CollectionControllerService getCollectionControllerService() { 1163 return collectionControllerService; 1164 } 1165 1166 public void setCollectionControllerService(CollectionControllerService collectionControllerService) { 1167 this.collectionControllerService = collectionControllerService; 1168 } 1169 1170 protected ParameterService getParameterService() { 1171 return parameterService; 1172 } 1173 1174 public void setParameterService(ParameterService parameterService) { 1175 this.parameterService = parameterService; 1176 } 1177}