001/** 002 * Copyright 2005-2017 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.kew.impl.document; 017 018import java.io.Serializable; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.commons.lang.StringUtils; 028import org.joda.time.DateTime; 029import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract; 030import org.kuali.rice.kew.api.KewApiConstants; 031import org.kuali.rice.kew.api.KewApiServiceLocator; 032import org.kuali.rice.kew.api.action.ActionRequest; 033import org.kuali.rice.kew.api.action.ActionRequestType; 034import org.kuali.rice.kew.api.action.ActionTaken; 035import org.kuali.rice.kew.api.action.ActionType; 036import org.kuali.rice.kew.api.action.AdHocRevoke; 037import org.kuali.rice.kew.api.action.AdHocToGroup; 038import org.kuali.rice.kew.api.action.AdHocToPrincipal; 039import org.kuali.rice.kew.api.action.DocumentActionParameters; 040import org.kuali.rice.kew.api.action.DocumentActionResult; 041import org.kuali.rice.kew.api.action.MovePoint; 042import org.kuali.rice.kew.api.action.RequestedActions; 043import org.kuali.rice.kew.api.action.ReturnPoint; 044import org.kuali.rice.kew.api.action.ValidActions; 045import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService; 046import org.kuali.rice.kew.api.document.Document; 047import org.kuali.rice.kew.api.document.DocumentContent; 048import org.kuali.rice.kew.api.document.DocumentContentUpdate; 049import org.kuali.rice.kew.api.document.DocumentDetail; 050import org.kuali.rice.kew.api.document.DocumentStatus; 051import org.kuali.rice.kew.api.document.DocumentUpdate; 052import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition; 053import org.kuali.rice.kew.api.document.node.RouteNodeInstance; 054import org.kuali.rice.kew.api.document.WorkflowDocumentService; 055 056/** 057 * The implementation of {@link org.kuali.rice.kew.api.WorkflowDocument}. Implements {@link WorkflowDocumentPrototype} to expose 058 * and initialization method used for construction. 059 * <p>NOTE: operations against document data on this are only "flushed" when an action is performed.</p> 060 * <p><b>This class is *not* thread safe.</b></p> 061 * @see org.kuali.rice.kew.api.WorkflowDocument 062 */ 063public class WorkflowDocumentImpl implements Serializable, WorkflowDocumentPrototype { 064 065 private static final long serialVersionUID = -3672966990721719088L; 066 067 /** 068 * The principal id under which all document actions will be performed. 069 */ 070 private String principalId; 071 /** 072 * Stores local changes that need to be committed. 073 */ 074 private ModifiableDocument modifiableDocument; 075 /** 076 * Stores local changes that need to be committed. 077 */ 078 private ModifiableDocumentContent modifiableDocumentContent; 079 /** 080 * Local cache of valid document actions. 081 * @see #getValidActions() 082 */ 083 private ValidActions validActions; 084 /** 085 * Local cache of requested document actions. 086 * @see #getRequestedActions() 087 */ 088 private RequestedActions requestedActions; 089 /** 090 * Flag that indicates whether the document has been deleted; if so the object is thereafter in an illegal state. 091 */ 092 private boolean documentDeleted = false; 093 094 private transient WorkflowDocumentActionsService workflowDocumentActionsService; 095 private transient WorkflowDocumentService workflowDocumentService; 096 097 public void init(String principalId, Document document) { 098 if (StringUtils.isBlank("principalId")) { 099 throw new IllegalArgumentException("principalId was null or blank"); 100 } 101 if (document == null) { 102 throw new IllegalArgumentException("document was null"); 103 } 104 this.principalId = principalId; 105 this.modifiableDocument = new ModifiableDocument(document); 106 this.modifiableDocumentContent = null; 107 this.validActions = null; 108 this.requestedActions = null; 109 } 110 111 public WorkflowDocumentActionsService getWorkflowDocumentActionsService() { 112 if (workflowDocumentActionsService == null) { 113 workflowDocumentActionsService = KewApiServiceLocator.getWorkflowDocumentActionsService(); 114 } 115 return workflowDocumentActionsService; 116 } 117 118 public void setWorkflowDocumentActionsService(WorkflowDocumentActionsService workflowDocumentActionsService) { 119 this.workflowDocumentActionsService = workflowDocumentActionsService; 120 } 121 122 public WorkflowDocumentService getWorkflowDocumentService() { 123 if (workflowDocumentService == null) { 124 workflowDocumentService = KewApiServiceLocator.getWorkflowDocumentService(); 125 } 126 return workflowDocumentService; 127 } 128 129 public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) { 130 this.workflowDocumentService = workflowDocumentService; 131 } 132 133 protected ModifiableDocument getModifiableDocument() { 134 return modifiableDocument; 135 } 136 137 protected ModifiableDocumentContent getModifiableDocumentContent() { 138 if (this.modifiableDocumentContent == null) { 139 DocumentContent documentContent = getWorkflowDocumentService().getDocumentContent(getDocumentId()); 140 if (documentContent == null) { 141 throw new IllegalStateException("Failed to load document content for documentId: " + getDocumentId()); 142 } 143 this.modifiableDocumentContent = new ModifiableDocumentContent(documentContent); 144 } 145 return this.modifiableDocumentContent; 146 } 147 148 @Override 149 public String getDocumentId() { 150 if (documentDeleted) { 151 throw new IllegalStateException("Document has been deleted."); 152 } 153 return getModifiableDocument().getDocumentId(); 154 } 155 156 @Override 157 public Document getDocument() { 158 return getModifiableDocument().getDocument(); 159 } 160 161 @Override 162 public DocumentContent getDocumentContent() { 163 return getModifiableDocumentContent().getDocumentContent(); 164 } 165 166 @Override 167 public String getApplicationContent() { 168 return getDocumentContent().getApplicationContent(); 169 } 170 171 @Override 172 public void setApplicationContent(String applicationContent) { 173 getModifiableDocumentContent().setApplicationContent(applicationContent); 174 } 175 176 @Override 177 public void setAttributeContent(String attributeContent) { 178 getModifiableDocumentContent().setAttributeContent(attributeContent); 179 } 180 181 @Override 182 public void clearAttributeContent() { 183 getModifiableDocumentContent().setAttributeContent(""); 184 } 185 186 @Override 187 public String getAttributeContent() { 188 return getDocumentContent().getAttributeContent(); 189 } 190 191 @Override 192 public void addAttributeDefinition(WorkflowAttributeDefinition attributeDefinition) { 193 getModifiableDocumentContent().addAttributeDefinition(attributeDefinition); 194 } 195 196 @Override 197 public void removeAttributeDefinition(WorkflowAttributeDefinition attributeDefinition) { 198 getModifiableDocumentContent().removeAttributeDefinition(attributeDefinition); 199 } 200 201 @Override 202 public void clearAttributeDefinitions() { 203 getAttributeDefinitions().clear(); 204 } 205 206 @Override 207 public List<WorkflowAttributeDefinition> getAttributeDefinitions() { 208 return getModifiableDocumentContent().getAttributeDefinitions(); 209 } 210 211 @Override 212 public void setSearchableContent(String searchableContent) { 213 getModifiableDocumentContent().setSearchableContent(searchableContent); 214 } 215 216 @Override 217 public void addSearchableDefinition(WorkflowAttributeDefinition searchableDefinition) { 218 getModifiableDocumentContent().addSearchableDefinition(searchableDefinition); 219 } 220 221 @Override 222 public void removeSearchableDefinition(WorkflowAttributeDefinition searchableDefinition) { 223 getModifiableDocumentContent().removeSearchableDefinition(searchableDefinition); 224 } 225 226 @Override 227 public void clearSearchableDefinitions() { 228 getSearchableDefinitions().clear(); 229 } 230 231 @Override 232 public void clearSearchableContent() { 233 getModifiableDocumentContent().setSearchableContent(""); 234 } 235 236 @Override 237 public List<WorkflowAttributeDefinition> getSearchableDefinitions() { 238 return getModifiableDocumentContent().getSearchableDefinitions(); 239 } 240 241 @Override 242 public List<? extends RemotableAttributeErrorContract> validateAttributeDefinition( 243 WorkflowAttributeDefinition attributeDefinition) { 244 return getWorkflowDocumentActionsService().validateWorkflowAttributeDefinition(attributeDefinition); 245 } 246 247 @Override 248 public List<ActionRequest> getRootActionRequests() { 249 return getWorkflowDocumentService().getRootActionRequests(getDocumentId()); 250 } 251 252 @Override 253 public List<ActionTaken> getActionsTaken() { 254 return getWorkflowDocumentService().getActionsTaken(getDocumentId()); 255 } 256 257 @Override 258 public void setApplicationDocumentId(String applicationDocumentId) { 259 getModifiableDocument().setApplicationDocumentId(applicationDocumentId); 260 } 261 262 @Override 263 public String getApplicationDocumentId() { 264 return getModifiableDocument().getApplicationDocumentId(); 265 } 266 267 @Override 268 public DateTime getDateCreated() { 269 return getModifiableDocument().getDateCreated(); 270 } 271 272 @Override 273 public String getTitle() { 274 return getModifiableDocument().getTitle(); 275 } 276 277 @Override 278 public ValidActions getValidActions() { 279 if (validActions == null) { 280 validActions = getWorkflowDocumentActionsService().determineValidActions(getDocumentId(), getPrincipalId()); 281 } 282 return validActions; 283 } 284 285 @Override 286 public RequestedActions getRequestedActions() { 287 if (requestedActions == null) { 288 requestedActions = getWorkflowDocumentActionsService().determineRequestedActions(getDocumentId(), 289 getPrincipalId()); 290 } 291 return requestedActions; 292 } 293 294 protected DocumentUpdate getDocumentUpdateIfDirty() { 295 if (getModifiableDocument().isDirty()) { 296 return getModifiableDocument().build(); 297 } 298 return null; 299 } 300 301 protected DocumentContentUpdate getDocumentContentUpdateIfDirty() { 302 if (getModifiableDocumentContent().isDirty()) { 303 return getModifiableDocumentContent().build(); 304 } 305 return null; 306 } 307 308 protected void resetStateAfterAction(DocumentActionResult response) { 309 this.modifiableDocument = new ModifiableDocument(response.getDocument()); 310 this.validActions = null; 311 if (response.getValidActions() != null) { 312 this.validActions = response.getValidActions(); 313 } 314 this.requestedActions = null; 315 if (response.getRequestedActions() != null) { 316 this.requestedActions = response.getRequestedActions(); 317 } 318 // regardless of whether modifiable document content is dirty, we null it out so it will be re-fetched next time it's needed 319 this.modifiableDocumentContent = null; 320 } 321 322 @Override 323 public void saveDocument(String annotation) { 324 DocumentActionResult result = getWorkflowDocumentActionsService().save( 325 constructDocumentActionParameters(annotation)); 326 resetStateAfterAction(result); 327 } 328 329 @Override 330 public void route(String annotation) { 331 DocumentActionResult result = getWorkflowDocumentActionsService().route( 332 constructDocumentActionParameters(annotation)); 333 resetStateAfterAction(result); 334 } 335 336 @Override 337 public void disapprove(String annotation) { 338 DocumentActionResult result = getWorkflowDocumentActionsService().disapprove( 339 constructDocumentActionParameters(annotation)); 340 resetStateAfterAction(result); 341 } 342 343 @Override 344 public void approve(String annotation) { 345 DocumentActionResult result = getWorkflowDocumentActionsService().approve( 346 constructDocumentActionParameters(annotation)); 347 resetStateAfterAction(result); 348 } 349 350 @Override 351 public void cancel(String annotation) { 352 DocumentActionResult result = getWorkflowDocumentActionsService().cancel( 353 constructDocumentActionParameters(annotation)); 354 resetStateAfterAction(result); 355 } 356 357 @Override 358 public void recall(String annotation, boolean cancel) { 359 DocumentActionResult result = getWorkflowDocumentActionsService().recall( 360 constructDocumentActionParameters(annotation), cancel); 361 resetStateAfterAction(result); 362 } 363 364 @Override 365 public void blanketApprove(String annotation) { 366 DocumentActionResult result = getWorkflowDocumentActionsService().blanketApprove( 367 constructDocumentActionParameters(annotation)); 368 resetStateAfterAction(result); 369 } 370 371 @Override 372 public void blanketApprove(String annotation, String... nodeNames) { 373 if (nodeNames == null) { 374 throw new IllegalArgumentException("nodeNames was null"); 375 } 376 Set<String> nodeNamesSet = new HashSet<String>(Arrays.asList(nodeNames)); 377 DocumentActionResult result = getWorkflowDocumentActionsService().blanketApproveToNodes( 378 constructDocumentActionParameters(annotation), nodeNamesSet); 379 resetStateAfterAction(result); 380 } 381 382 @Override 383 public void saveDocumentData() { 384 DocumentActionResult result = getWorkflowDocumentActionsService().saveDocumentData( 385 constructDocumentActionParameters(null)); 386 resetStateAfterAction(result); 387 } 388 389 @Override 390 public void setApplicationDocumentStatus(String applicationDocumentStatus) { 391 getModifiableDocument().setApplicationDocumentStatus(applicationDocumentStatus); 392 } 393 394 @Override 395 public void acknowledge(String annotation) { 396 DocumentActionResult result = getWorkflowDocumentActionsService().acknowledge( 397 constructDocumentActionParameters(annotation)); 398 resetStateAfterAction(result); 399 } 400 401 @Override 402 public void fyi(String annotation) { 403 DocumentActionResult result = getWorkflowDocumentActionsService().clearFyi( 404 constructDocumentActionParameters(annotation)); 405 resetStateAfterAction(result); 406 } 407 408 @Override 409 public void fyi() { 410 fyi(""); 411 } 412 413 @Override 414 public void delete() { 415 getWorkflowDocumentActionsService().delete(getDocumentId(), principalId); 416 documentDeleted = true; 417 } 418 419 @Override 420 public void refresh() { 421 Document document = getWorkflowDocumentService().getDocument(getDocumentId()); 422 this.modifiableDocument = new ModifiableDocument(document); 423 this.validActions = null; 424 this.requestedActions = null; 425 this.modifiableDocumentContent = null; 426 } 427 428 @Override 429 public void adHocToPrincipal(ActionRequestType actionRequested, String annotation, String targetPrincipalId, 430 String responsibilityDescription, boolean forceAction) { 431 adHocToPrincipal(actionRequested, null, annotation, targetPrincipalId, responsibilityDescription, forceAction); 432 } 433 434 @Override 435 public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation, 436 String targetPrincipalId, String responsibilityDescription, boolean forceAction) { 437 adHocToPrincipal(actionRequested, nodeName, annotation, targetPrincipalId, responsibilityDescription, 438 forceAction, null); 439 } 440 441 @Override 442 public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation, 443 String targetPrincipalId, String responsibilityDescription, boolean forceAction, String requestLabel) { 444 AdHocToPrincipal.Builder builder = AdHocToPrincipal.Builder 445 .create(actionRequested, nodeName, targetPrincipalId); 446 builder.setResponsibilityDescription(responsibilityDescription); 447 builder.setForceAction(forceAction); 448 builder.setRequestLabel(requestLabel); 449 DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal( 450 constructDocumentActionParameters(annotation), builder.build()); 451 resetStateAfterAction(result); 452 } 453 454 @Override 455 public void adHocToPrincipal(AdHocToPrincipal adHocToPrincipal, String annotation) { 456 DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal( 457 constructDocumentActionParameters(annotation), adHocToPrincipal); 458 resetStateAfterAction(result); 459 } 460 461 @Override 462 public void adHocToGroup(ActionRequestType actionRequested, String annotation, String targetGroupId, 463 String responsibilityDescription, boolean forceAction) { 464 adHocToGroup(actionRequested, null, annotation, targetGroupId, responsibilityDescription, forceAction); 465 } 466 467 @Override 468 public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation, 469 String targetGroupId, String responsibilityDescription, boolean forceAction) { 470 adHocToGroup(actionRequested, nodeName, annotation, targetGroupId, responsibilityDescription, forceAction, null); 471 } 472 473 @Override 474 public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation, 475 String targetGroupId, String responsibilityDescription, boolean forceAction, String requestLabel) { 476 AdHocToGroup.Builder builder = AdHocToGroup.Builder.create(actionRequested, nodeName, targetGroupId); 477 builder.setResponsibilityDescription(responsibilityDescription); 478 builder.setForceAction(forceAction); 479 builder.setRequestLabel(requestLabel); 480 DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup( 481 constructDocumentActionParameters(annotation), builder.build()); 482 resetStateAfterAction(result); 483 } 484 485 @Override 486 public void adHocToGroup(AdHocToGroup adHocToGroup, String annotation) { 487 DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup( 488 constructDocumentActionParameters(annotation), adHocToGroup); 489 resetStateAfterAction(result); 490 } 491 492 @Override 493 public void revokeAdHocRequestById(String actionRequestId, String annotation) { 494 if (StringUtils.isBlank(actionRequestId)) { 495 throw new IllegalArgumentException("actionRequestId was null or blank"); 496 } 497 DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequestById( 498 constructDocumentActionParameters(annotation), actionRequestId); 499 resetStateAfterAction(result); 500 } 501 502 @Override 503 public void revokeAdHocRequests(AdHocRevoke revoke, String annotation) { 504 if (revoke == null) { 505 throw new IllegalArgumentException("revokeFromPrincipal was null"); 506 } 507 DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequests( 508 constructDocumentActionParameters(annotation), revoke); 509 resetStateAfterAction(result); 510 } 511 512 @Override 513 public void revokeAllAdHocRequests(String annotation) { 514 DocumentActionResult result = getWorkflowDocumentActionsService().revokeAllAdHocRequests( 515 constructDocumentActionParameters(annotation)); 516 resetStateAfterAction(result); 517 } 518 519 @Override 520 public void setTitle(String title) { 521 getModifiableDocument().setTitle(title); 522 } 523 524 @Override 525 public String getDocumentTypeName() { 526 return getDocument().getDocumentTypeName(); 527 } 528 529 @Override 530 public boolean isCompletionRequested() { 531 return getRequestedActions().isCompleteRequested(); 532 } 533 534 @Override 535 public boolean isApprovalRequested() { 536 return getRequestedActions().isApproveRequested(); 537 } 538 539 @Override 540 public boolean isAcknowledgeRequested() { 541 return getRequestedActions().isAcknowledgeRequested(); 542 } 543 544 @Override 545 public boolean isFYIRequested() { 546 return getRequestedActions().isFyiRequested(); 547 } 548 549 @Override 550 public boolean isBlanketApproveCapable() { 551 return isValidAction(ActionType.BLANKET_APPROVE) 552 && (isCompletionRequested() || isApprovalRequested() || isInitiated()); 553 } 554 555 @Override 556 public boolean isRouteCapable() { 557 return isValidAction(ActionType.ROUTE); 558 } 559 560 @Override 561 public boolean isValidAction(ActionType actionType) { 562 if (actionType == null) { 563 throw new IllegalArgumentException("actionType was null"); 564 } 565 566 return getWorkflowDocumentActionsService().isValidAction(actionType.getCode(), getDocumentId(), 567 getPrincipalId()); 568 } 569 570 @Override 571 public void superUserBlanketApprove(String annotation) { 572 DocumentActionResult result = getWorkflowDocumentActionsService().superUserBlanketApprove( 573 constructDocumentActionParameters(annotation), true); 574 resetStateAfterAction(result); 575 } 576 577 @Override 578 public void superUserNodeApprove(String nodeName, String annotation) { 579 DocumentActionResult result = getWorkflowDocumentActionsService().superUserNodeApprove( 580 constructDocumentActionParameters(annotation), true, nodeName); 581 resetStateAfterAction(result); 582 } 583 584 @Override 585 public void superUserTakeRequestedAction(String actionRequestId, String annotation) { 586 DocumentActionResult result = getWorkflowDocumentActionsService().superUserTakeRequestedAction( 587 constructDocumentActionParameters(annotation), true, actionRequestId); 588 resetStateAfterAction(result); 589 } 590 591 @Override 592 public void superUserDisapprove(String annotation) { 593 DocumentActionResult result = getWorkflowDocumentActionsService().superUserDisapprove( 594 constructDocumentActionParameters(annotation), true); 595 resetStateAfterAction(result); 596 } 597 598 @Override 599 public void superUserCancel(String annotation) { 600 DocumentActionResult result = getWorkflowDocumentActionsService().superUserCancel( 601 constructDocumentActionParameters(annotation), true); 602 resetStateAfterAction(result); 603 } 604 605 @Override 606 public void superUserReturnToPreviousNode(ReturnPoint returnPoint, String annotation) { 607 DocumentActionResult result = getWorkflowDocumentActionsService().superUserReturnToPreviousNode( 608 constructDocumentActionParameters(annotation), true, returnPoint); 609 resetStateAfterAction(result); 610 } 611 612 @Override 613 public void complete(String annotation) { 614 DocumentActionResult result = getWorkflowDocumentActionsService().complete( 615 constructDocumentActionParameters(annotation)); 616 resetStateAfterAction(result); 617 } 618 619 @Override 620 public void logAnnotation(String annotation) { 621 getWorkflowDocumentActionsService().logAnnotation(getDocumentId(), principalId, annotation); 622 } 623 624 @Override 625 public DocumentStatus getStatus() { 626 return getDocument().getStatus(); 627 } 628 629 @Override 630 public boolean checkStatus(DocumentStatus status) { 631 if (status == null) { 632 throw new IllegalArgumentException("status was null"); 633 } 634 return status == getStatus(); 635 } 636 637 /** 638 * Indicates if the document is in the initiated state or not. 639 * 640 * @return true if in the specified state 641 */ 642 @Override 643 public boolean isInitiated() { 644 return checkStatus(DocumentStatus.INITIATED); 645 } 646 647 /** 648 * Indicates if the document is in the saved state or not. 649 * 650 * @return true if in the specified state 651 */ 652 @Override 653 public boolean isSaved() { 654 return checkStatus(DocumentStatus.SAVED); 655 } 656 657 /** 658 * Indicates if the document is in the enroute state or not. 659 * 660 * @return true if in the specified state 661 */ 662 @Override 663 public boolean isEnroute() { 664 return checkStatus(DocumentStatus.ENROUTE); 665 } 666 667 /** 668 * Indicates if the document is in the exception state or not. 669 * 670 * @return true if in the specified state 671 */ 672 @Override 673 public boolean isException() { 674 return checkStatus(DocumentStatus.EXCEPTION); 675 } 676 677 /** 678 * Indicates if the document is in the canceled state or not. 679 * 680 * @return true if in the specified state 681 */ 682 @Override 683 public boolean isCanceled() { 684 return checkStatus(DocumentStatus.CANCELED); 685 } 686 687 /** 688 * Indicates if the document is in the recalled state or not. 689 * 690 * @return true if in the specified state 691 */ 692 @Override 693 public boolean isRecalled() { 694 return checkStatus(DocumentStatus.RECALLED); 695 } 696 697 /** 698 * Indicates if the document is in the disapproved state or not. 699 * 700 * @return true if in the specified state 701 */ 702 @Override 703 public boolean isDisapproved() { 704 return checkStatus(DocumentStatus.DISAPPROVED); 705 } 706 707 /** 708 * Indicates if the document is in the Processed or Finalized state. 709 * 710 * @return true if in the specified state 711 */ 712 @Override 713 public boolean isApproved() { 714 return isProcessed() || isFinal(); 715 } 716 717 /** 718 * Indicates if the document is in the processed state or not. 719 * 720 * @return true if in the specified state 721 */ 722 @Override 723 public boolean isProcessed() { 724 return checkStatus(DocumentStatus.PROCESSED); 725 } 726 727 /** 728 * Indicates if the document is in the final state or not. 729 * 730 * @return true if in the specified state 731 */ 732 @Override 733 public boolean isFinal() { 734 return checkStatus(DocumentStatus.FINAL); 735 } 736 737 /** 738 * Returns the principalId with which this WorkflowDocument was constructed 739 * 740 * @return the principalId with which this WorkflowDocument was constructed 741 */ 742 @Override 743 public String getPrincipalId() { 744 return principalId; 745 } 746 747 @Override 748 public void switchPrincipal(String principalId) { 749 if (StringUtils.isBlank(this.principalId)) { 750 throw new IllegalArgumentException("principalId was null or blank"); 751 } 752 this.principalId = principalId; 753 this.validActions = null; 754 this.requestedActions = null; 755 } 756 757 @Override 758 public void takeGroupAuthority(String annotation, String groupId) { 759 DocumentActionResult result = getWorkflowDocumentActionsService().takeGroupAuthority( 760 constructDocumentActionParameters(annotation), groupId); 761 resetStateAfterAction(result); 762 } 763 764 @Override 765 public void releaseGroupAuthority(String annotation, String groupId) { 766 DocumentActionResult result = getWorkflowDocumentActionsService().releaseGroupAuthority( 767 constructDocumentActionParameters(annotation), groupId); 768 resetStateAfterAction(result); 769 } 770 771 @Override 772 public Set<String> getNodeNames() { 773 final List<String> names = getWorkflowDocumentService().getActiveRouteNodeNames(getDocumentId()); 774 return Collections.unmodifiableSet(new HashSet<String>(names)); 775 } 776 777 @Override 778 public Set<String> getSimpleNodeNames() { 779 final List<String> names = getWorkflowDocumentService().getActiveSimpleRouteNodeNames(getDocumentId()); 780 return Collections.unmodifiableSet(new HashSet<String>(names)); 781 } 782 783 @Override 784 public Set<String> getCurrentNodeNames() { 785 final List<String> names = getWorkflowDocumentService().getCurrentRouteNodeNames(getDocumentId()); 786 return Collections.unmodifiableSet(new HashSet<String>(names)); 787 } 788 789 @Override 790 public Set<String> getCurrentSimpleNodeNames() { 791 final List<String> names = getWorkflowDocumentService().getCurrentSimpleRouteNodeNames(getDocumentId()); 792 return Collections.unmodifiableSet(new HashSet<String>(names));} 793 794 795 @Override 796 public void returnToPreviousNode(String annotation, String nodeName) { 797 if (nodeName == null) { 798 throw new IllegalArgumentException("nodeName was null"); 799 } 800 returnToPreviousNode(annotation, ReturnPoint.create(nodeName)); 801 } 802 803 @Override 804 public void returnToPreviousNode(String annotation, ReturnPoint returnPoint) { 805 if (returnPoint == null) { 806 throw new IllegalArgumentException("returnPoint was null"); 807 } 808 DocumentActionResult result = getWorkflowDocumentActionsService().returnToPreviousNode( 809 constructDocumentActionParameters(annotation), returnPoint); 810 resetStateAfterAction(result); 811 } 812 813 @Override 814 public void move(MovePoint movePoint, String annotation) { 815 if (movePoint == null) { 816 throw new IllegalArgumentException("movePoint was null"); 817 } 818 DocumentActionResult result = getWorkflowDocumentActionsService().move( 819 constructDocumentActionParameters(annotation), movePoint); 820 resetStateAfterAction(result); 821 } 822 823 @Override 824 public List<RouteNodeInstance> getActiveRouteNodeInstances() { 825 return getWorkflowDocumentService().getActiveRouteNodeInstances(getDocumentId()); 826 } 827 828 @Override 829 public List<RouteNodeInstance> getCurrentRouteNodeInstances() { 830 return getWorkflowDocumentService().getCurrentRouteNodeInstances(getDocumentId()); 831 } 832 833 @Override 834 public List<RouteNodeInstance> getRouteNodeInstances() { 835 return getWorkflowDocumentService().getRouteNodeInstances(getDocumentId()); 836 } 837 838 @Override 839 public List<String> getPreviousNodeNames() { 840 return getWorkflowDocumentService().getPreviousRouteNodeNames(getDocumentId()); 841 } 842 843 @Override 844 public DocumentDetail getDocumentDetail() { 845 return getWorkflowDocumentService().getDocumentDetail(getDocumentId()); 846 } 847 848 @Override 849 public void updateDocumentContent(DocumentContentUpdate documentContentUpdate) { 850 if (documentContentUpdate == null) { 851 throw new IllegalArgumentException("documentContentUpdate was null."); 852 } 853 getModifiableDocumentContent().setDocumentContentUpdate(documentContentUpdate); 854 } 855 856 @Override 857 public void placeInExceptionRouting(String annotation) { 858 DocumentActionResult result = getWorkflowDocumentActionsService().placeInExceptionRouting( 859 constructDocumentActionParameters(annotation)); 860 resetStateAfterAction(result); 861 } 862 863 @Override 864 public void setVariable(String name, String value) { 865 getModifiableDocument().setVariable(name, value); 866 } 867 868 @Override 869 public String getVariableValue(String name) { 870 return getModifiableDocument().getVariableValue(name); 871 } 872 873 @Override 874 public void setReceiveFutureRequests() { 875 setVariable(getFutureRequestsKey(principalId), getReceiveFutureRequestsValue()); 876 } 877 878 @Override 879 public void setDoNotReceiveFutureRequests() { 880 this.setVariable(getFutureRequestsKey(principalId), getDoNotReceiveFutureRequestsValue()); 881 } 882 883 @Override 884 public void setClearFutureRequests() { 885 this.setVariable(getFutureRequestsKey(principalId), getClearFutureRequestsValue()); 886 } 887 888 protected String getFutureRequestsKey(String principalId) { 889 return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + "," 890 + new Date().toString() + ", " + Math.random(); 891 } 892 893 @Override 894 public String getReceiveFutureRequestsValue() { 895 return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 896 } 897 898 @Override 899 public String getDoNotReceiveFutureRequestsValue() { 900 return KewApiConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 901 } 902 903 @Override 904 public String getClearFutureRequestsValue() { 905 return KewApiConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 906 } 907 908 protected DocumentActionParameters constructDocumentActionParameters(String annotation) { 909 DocumentActionParameters.Builder builder = DocumentActionParameters.Builder.create(getDocumentId(), 910 getPrincipalId()); 911 builder.setAnnotation(annotation); 912 builder.setDocumentUpdate(getDocumentUpdateIfDirty()); 913 builder.setDocumentContentUpdate(getDocumentContentUpdateIfDirty()); 914 return builder.build(); 915 } 916 917 @Override 918 public DateTime getDateLastModified() { 919 return getDocument().getDateLastModified(); 920 } 921 922 @Override 923 public DateTime getDateApproved() { 924 return getDocument().getDateApproved(); 925 } 926 927 @Override 928 public DateTime getDateFinalized() { 929 return getDocument().getDateFinalized(); 930 } 931 932 @Override 933 public String getInitiatorPrincipalId() { 934 return getDocument().getInitiatorPrincipalId(); 935 } 936 937 @Override 938 public String getRoutedByPrincipalId() { 939 return getDocument().getRoutedByPrincipalId(); 940 } 941 942 @Override 943 public String getDocumentTypeId() { 944 return getDocument().getDocumentTypeId(); 945 } 946 947 @Override 948 public String getDocumentHandlerUrl() { 949 return getDocument().getDocumentHandlerUrl(); 950 } 951 952 @Override 953 public String getApplicationDocumentStatus() { 954 return getDocument().getApplicationDocumentStatus(); 955 } 956 957 @Override 958 public DateTime getApplicationDocumentStatusDate() { 959 return getDocument().getApplicationDocumentStatusDate(); 960 } 961 962 @Override 963 public Map<String, String> getVariables() { 964 return getDocument().getVariables(); 965 } 966 967 /** 968 * A wrapper around DocumentContent which keeps track of local changes and generates 969 * a new updated DocumentContent as necessary. 970 */ 971 protected static class ModifiableDocumentContent implements Serializable { 972 973 private static final long serialVersionUID = -4458431160327214042L; 974 975 private boolean dirty; 976 private DocumentContent originalDocumentContent; 977 private DocumentContentUpdate.Builder builder; 978 979 protected ModifiableDocumentContent(DocumentContent documentContent) { 980 this.dirty = false; 981 this.originalDocumentContent = documentContent; 982 this.builder = DocumentContentUpdate.Builder.create(documentContent); 983 } 984 985 protected DocumentContent getDocumentContent() { 986 if (!dirty) { 987 return originalDocumentContent; 988 } 989 DocumentContent.Builder documentContentBuilder = DocumentContent.Builder.create(originalDocumentContent); 990 documentContentBuilder.setApplicationContent(builder.getApplicationContent()); 991 documentContentBuilder.setAttributeContent(builder.getAttributeContent()); 992 documentContentBuilder.setSearchableContent(builder.getSearchableContent()); 993 return documentContentBuilder.build(); 994 } 995 996 protected DocumentContentUpdate build() { 997 return builder.build(); 998 } 999 1000 protected void setDocumentContentUpdate(DocumentContentUpdate update) { 1001 this.builder = DocumentContentUpdate.Builder.create(update); 1002 this.dirty = true; 1003 } 1004 1005 protected void addAttributeDefinition(WorkflowAttributeDefinition definition) { 1006 builder.getAttributeDefinitions().add(definition); 1007 dirty = true; 1008 } 1009 1010 protected void removeAttributeDefinition(WorkflowAttributeDefinition definition) { 1011 builder.getAttributeDefinitions().remove(definition); 1012 dirty = true; 1013 } 1014 1015 protected List<WorkflowAttributeDefinition> getAttributeDefinitions() { 1016 return builder.getAttributeDefinitions(); 1017 } 1018 1019 protected void addSearchableDefinition(WorkflowAttributeDefinition definition) { 1020 builder.getSearchableDefinitions().add(definition); 1021 dirty = true; 1022 } 1023 1024 protected void removeSearchableDefinition(WorkflowAttributeDefinition definition) { 1025 builder.getSearchableDefinitions().remove(definition); 1026 dirty = true; 1027 } 1028 1029 protected List<WorkflowAttributeDefinition> getSearchableDefinitions() { 1030 return builder.getAttributeDefinitions(); 1031 } 1032 1033 protected void setApplicationContent(String applicationContent) { 1034 if ( !StringUtils.equals(applicationContent, builder.getApplicationContent() ) ) { 1035 builder.setApplicationContent(applicationContent); 1036 dirty = true; 1037 } 1038 } 1039 1040 protected void setAttributeContent(String attributeContent) { 1041 if ( !StringUtils.equals(attributeContent, builder.getAttributeContent() ) ) { 1042 builder.setAttributeContent(attributeContent); 1043 dirty = true; 1044 } 1045 } 1046 1047 public void setAttributeDefinitions(List<WorkflowAttributeDefinition> attributeDefinitions) { 1048 builder.setAttributeDefinitions(attributeDefinitions); 1049 dirty = true; 1050 } 1051 1052 public void setSearchableContent(String searchableContent) { 1053 if ( !StringUtils.equals(searchableContent, builder.getSearchableContent() ) ) { 1054 builder.setSearchableContent(searchableContent); 1055 dirty = true; 1056 } 1057 } 1058 1059 public void setSearchableDefinitions(List<WorkflowAttributeDefinition> searchableDefinitions) { 1060 builder.setSearchableDefinitions(searchableDefinitions); 1061 dirty = true; 1062 } 1063 1064 boolean isDirty() { 1065 return dirty; 1066 } 1067 1068 } 1069 1070 /** 1071 * A wrapper around Document which keeps track of local changes and generates 1072 * a new updated Document as necessary. 1073 */ 1074 protected static class ModifiableDocument implements Serializable { 1075 1076 private static final long serialVersionUID = -3234793238863410378L; 1077 1078 private boolean dirty; 1079 private Document originalDocument; 1080 private DocumentUpdate.Builder builder; 1081 1082 protected ModifiableDocument(Document document) { 1083 this.dirty = false; 1084 this.originalDocument = document; 1085 this.builder = DocumentUpdate.Builder.create(document); 1086 } 1087 1088 protected Document getDocument() { 1089 if (!dirty) { 1090 return originalDocument; 1091 } 1092 Document.Builder documentBuilder = Document.Builder.create(originalDocument); 1093 documentBuilder.setApplicationDocumentId(builder.getApplicationDocumentId()); 1094 documentBuilder.setTitle(builder.getTitle()); 1095 documentBuilder.setApplicationDocumentStatus(builder.getApplicationDocumentStatus()); 1096 documentBuilder.setVariables(builder.getVariables()); 1097 return documentBuilder.build(); 1098 } 1099 1100 protected DocumentUpdate build() { 1101 return builder.build(); 1102 } 1103 1104 /** 1105 * Immutable value which is accessed frequently, provide direct access to it. 1106 */ 1107 protected String getDocumentId() { 1108 return originalDocument.getDocumentId(); 1109 } 1110 1111 /** 1112 * Immutable value which is accessed frequently, provide direct access to it. 1113 */ 1114 protected DateTime getDateCreated() { 1115 return originalDocument.getDateCreated(); 1116 } 1117 1118 protected String getApplicationDocumentId() { 1119 return builder.getApplicationDocumentId(); 1120 } 1121 1122 protected void setApplicationDocumentId(String applicationDocumentId) { 1123 if ( !StringUtils.equals(applicationDocumentId, builder.getApplicationDocumentId() ) ) { 1124 builder.setApplicationDocumentId(applicationDocumentId); 1125 dirty = true; 1126 addDirtyField("applicationDocumentId"); 1127 } 1128 } 1129 1130 protected String getTitle() { 1131 return builder.getTitle(); 1132 } 1133 1134 protected void setTitle(String title) { 1135 if ( !StringUtils.equals(title, builder.getTitle() ) ) { 1136 builder.setTitle(title); 1137 dirty = true; 1138 addDirtyField("title"); 1139 } 1140 } 1141 1142 protected String getApplicationDocumentStatus() { 1143 return builder.getApplicationDocumentStatus(); 1144 } 1145 1146 protected void setApplicationDocumentStatus(String applicationDocumentStatus) { 1147 if ( !StringUtils.equals(applicationDocumentStatus, builder.getApplicationDocumentStatus() ) ) { 1148 builder.setApplicationDocumentStatus(applicationDocumentStatus); 1149 dirty = true; 1150 addDirtyField("applicationDocumentStatus"); 1151 } 1152 } 1153 1154 protected void setVariable(String name, String value) { 1155 if ( !StringUtils.equals(value, builder.getVariableValue(name) ) ) { 1156 builder.setVariable(name, value); 1157 dirty = true; 1158 addDirtyField("var[" + name + "]"); 1159 } 1160 } 1161 1162 protected String getVariableValue(String name) { 1163 return builder.getVariableValue(name); 1164 } 1165 1166 boolean isDirty() { 1167 return dirty; 1168 } 1169 1170 void addDirtyField(String field) { 1171 builder.addDirtyField(field); 1172 } 1173 1174 } 1175 1176}