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.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 public Set<String> getCurrentNodeNames() { 778 final List<String> names = getWorkflowDocumentService().getCurrentRouteNodeNames(getDocumentId()); 779 return Collections.unmodifiableSet(new HashSet<String>(names)); 780 } 781 782 @Override 783 public void returnToPreviousNode(String annotation, String nodeName) { 784 if (nodeName == null) { 785 throw new IllegalArgumentException("nodeName was null"); 786 } 787 returnToPreviousNode(annotation, ReturnPoint.create(nodeName)); 788 } 789 790 @Override 791 public void returnToPreviousNode(String annotation, ReturnPoint returnPoint) { 792 if (returnPoint == null) { 793 throw new IllegalArgumentException("returnPoint was null"); 794 } 795 DocumentActionResult result = getWorkflowDocumentActionsService().returnToPreviousNode( 796 constructDocumentActionParameters(annotation), returnPoint); 797 resetStateAfterAction(result); 798 } 799 800 @Override 801 public void move(MovePoint movePoint, String annotation) { 802 if (movePoint == null) { 803 throw new IllegalArgumentException("movePoint was null"); 804 } 805 DocumentActionResult result = getWorkflowDocumentActionsService().move( 806 constructDocumentActionParameters(annotation), movePoint); 807 resetStateAfterAction(result); 808 } 809 810 @Override 811 public List<RouteNodeInstance> getActiveRouteNodeInstances() { 812 return getWorkflowDocumentService().getActiveRouteNodeInstances(getDocumentId()); 813 } 814 815 @Override 816 public List<RouteNodeInstance> getCurrentRouteNodeInstances() { 817 return getWorkflowDocumentService().getCurrentRouteNodeInstances(getDocumentId()); 818 } 819 820 @Override 821 public List<RouteNodeInstance> getRouteNodeInstances() { 822 return getWorkflowDocumentService().getRouteNodeInstances(getDocumentId()); 823 } 824 825 @Override 826 public List<String> getPreviousNodeNames() { 827 return getWorkflowDocumentService().getPreviousRouteNodeNames(getDocumentId()); 828 } 829 830 @Override 831 public DocumentDetail getDocumentDetail() { 832 return getWorkflowDocumentService().getDocumentDetail(getDocumentId()); 833 } 834 835 @Override 836 public void updateDocumentContent(DocumentContentUpdate documentContentUpdate) { 837 if (documentContentUpdate == null) { 838 throw new IllegalArgumentException("documentContentUpdate was null."); 839 } 840 getModifiableDocumentContent().setDocumentContentUpdate(documentContentUpdate); 841 } 842 843 @Override 844 public void placeInExceptionRouting(String annotation) { 845 DocumentActionResult result = getWorkflowDocumentActionsService().placeInExceptionRouting( 846 constructDocumentActionParameters(annotation)); 847 resetStateAfterAction(result); 848 } 849 850 @Override 851 public void setVariable(String name, String value) { 852 getModifiableDocument().setVariable(name, value); 853 } 854 855 @Override 856 public String getVariableValue(String name) { 857 return getModifiableDocument().getVariableValue(name); 858 } 859 860 @Override 861 public void setReceiveFutureRequests() { 862 setVariable(getFutureRequestsKey(principalId), getReceiveFutureRequestsValue()); 863 } 864 865 @Override 866 public void setDoNotReceiveFutureRequests() { 867 this.setVariable(getFutureRequestsKey(principalId), getDoNotReceiveFutureRequestsValue()); 868 } 869 870 @Override 871 public void setClearFutureRequests() { 872 this.setVariable(getFutureRequestsKey(principalId), getClearFutureRequestsValue()); 873 } 874 875 protected String getFutureRequestsKey(String principalId) { 876 return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + "," 877 + new Date().toString() + ", " + Math.random(); 878 } 879 880 @Override 881 public String getReceiveFutureRequestsValue() { 882 return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 883 } 884 885 @Override 886 public String getDoNotReceiveFutureRequestsValue() { 887 return KewApiConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 888 } 889 890 @Override 891 public String getClearFutureRequestsValue() { 892 return KewApiConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE; 893 } 894 895 protected DocumentActionParameters constructDocumentActionParameters(String annotation) { 896 DocumentActionParameters.Builder builder = DocumentActionParameters.Builder.create(getDocumentId(), 897 getPrincipalId()); 898 builder.setAnnotation(annotation); 899 builder.setDocumentUpdate(getDocumentUpdateIfDirty()); 900 builder.setDocumentContentUpdate(getDocumentContentUpdateIfDirty()); 901 return builder.build(); 902 } 903 904 @Override 905 public DateTime getDateLastModified() { 906 return getDocument().getDateLastModified(); 907 } 908 909 @Override 910 public DateTime getDateApproved() { 911 return getDocument().getDateApproved(); 912 } 913 914 @Override 915 public DateTime getDateFinalized() { 916 return getDocument().getDateFinalized(); 917 } 918 919 @Override 920 public String getInitiatorPrincipalId() { 921 return getDocument().getInitiatorPrincipalId(); 922 } 923 924 @Override 925 public String getRoutedByPrincipalId() { 926 return getDocument().getRoutedByPrincipalId(); 927 } 928 929 @Override 930 public String getDocumentTypeId() { 931 return getDocument().getDocumentTypeId(); 932 } 933 934 @Override 935 public String getDocumentHandlerUrl() { 936 return getDocument().getDocumentHandlerUrl(); 937 } 938 939 @Override 940 public String getApplicationDocumentStatus() { 941 return getDocument().getApplicationDocumentStatus(); 942 } 943 944 @Override 945 public DateTime getApplicationDocumentStatusDate() { 946 return getDocument().getApplicationDocumentStatusDate(); 947 } 948 949 @Override 950 public Map<String, String> getVariables() { 951 return getDocument().getVariables(); 952 } 953 954 /** 955 * A wrapper around DocumentContent which keeps track of local changes and generates 956 * a new updated DocumentContent as necessary. 957 */ 958 protected static class ModifiableDocumentContent implements Serializable { 959 960 private static final long serialVersionUID = -4458431160327214042L; 961 962 private boolean dirty; 963 private DocumentContent originalDocumentContent; 964 private DocumentContentUpdate.Builder builder; 965 966 protected ModifiableDocumentContent(DocumentContent documentContent) { 967 this.dirty = false; 968 this.originalDocumentContent = documentContent; 969 this.builder = DocumentContentUpdate.Builder.create(documentContent); 970 } 971 972 protected DocumentContent getDocumentContent() { 973 if (!dirty) { 974 return originalDocumentContent; 975 } 976 DocumentContent.Builder documentContentBuilder = DocumentContent.Builder.create(originalDocumentContent); 977 documentContentBuilder.setApplicationContent(builder.getApplicationContent()); 978 documentContentBuilder.setAttributeContent(builder.getAttributeContent()); 979 documentContentBuilder.setSearchableContent(builder.getSearchableContent()); 980 return documentContentBuilder.build(); 981 } 982 983 protected DocumentContentUpdate build() { 984 return builder.build(); 985 } 986 987 protected void setDocumentContentUpdate(DocumentContentUpdate update) { 988 this.builder = DocumentContentUpdate.Builder.create(update); 989 this.dirty = true; 990 } 991 992 protected void addAttributeDefinition(WorkflowAttributeDefinition definition) { 993 builder.getAttributeDefinitions().add(definition); 994 dirty = true; 995 } 996 997 protected void removeAttributeDefinition(WorkflowAttributeDefinition definition) { 998 builder.getAttributeDefinitions().remove(definition); 999 dirty = true; 1000 } 1001 1002 protected List<WorkflowAttributeDefinition> getAttributeDefinitions() { 1003 return builder.getAttributeDefinitions(); 1004 } 1005 1006 protected void addSearchableDefinition(WorkflowAttributeDefinition definition) { 1007 builder.getSearchableDefinitions().add(definition); 1008 dirty = true; 1009 } 1010 1011 protected void removeSearchableDefinition(WorkflowAttributeDefinition definition) { 1012 builder.getSearchableDefinitions().remove(definition); 1013 dirty = true; 1014 } 1015 1016 protected List<WorkflowAttributeDefinition> getSearchableDefinitions() { 1017 return builder.getAttributeDefinitions(); 1018 } 1019 1020 protected void setApplicationContent(String applicationContent) { 1021 if ( !StringUtils.equals(applicationContent, builder.getApplicationContent() ) ) { 1022 builder.setApplicationContent(applicationContent); 1023 dirty = true; 1024 } 1025 } 1026 1027 protected void setAttributeContent(String attributeContent) { 1028 if ( !StringUtils.equals(attributeContent, builder.getAttributeContent() ) ) { 1029 builder.setAttributeContent(attributeContent); 1030 dirty = true; 1031 } 1032 } 1033 1034 public void setAttributeDefinitions(List<WorkflowAttributeDefinition> attributeDefinitions) { 1035 builder.setAttributeDefinitions(attributeDefinitions); 1036 dirty = true; 1037 } 1038 1039 public void setSearchableContent(String searchableContent) { 1040 if ( !StringUtils.equals(searchableContent, builder.getSearchableContent() ) ) { 1041 builder.setSearchableContent(searchableContent); 1042 dirty = true; 1043 } 1044 } 1045 1046 public void setSearchableDefinitions(List<WorkflowAttributeDefinition> searchableDefinitions) { 1047 builder.setSearchableDefinitions(searchableDefinitions); 1048 dirty = true; 1049 } 1050 1051 boolean isDirty() { 1052 return dirty; 1053 } 1054 1055 } 1056 1057 /** 1058 * A wrapper around Document which keeps track of local changes and generates 1059 * a new updated Document as necessary. 1060 */ 1061 protected static class ModifiableDocument implements Serializable { 1062 1063 private static final long serialVersionUID = -3234793238863410378L; 1064 1065 private boolean dirty; 1066 private Document originalDocument; 1067 private DocumentUpdate.Builder builder; 1068 1069 protected ModifiableDocument(Document document) { 1070 this.dirty = false; 1071 this.originalDocument = document; 1072 this.builder = DocumentUpdate.Builder.create(document); 1073 } 1074 1075 protected Document getDocument() { 1076 if (!dirty) { 1077 return originalDocument; 1078 } 1079 Document.Builder documentBuilder = Document.Builder.create(originalDocument); 1080 documentBuilder.setApplicationDocumentId(builder.getApplicationDocumentId()); 1081 documentBuilder.setTitle(builder.getTitle()); 1082 documentBuilder.setApplicationDocumentStatus(builder.getApplicationDocumentStatus()); 1083 documentBuilder.setVariables(builder.getVariables()); 1084 return documentBuilder.build(); 1085 } 1086 1087 protected DocumentUpdate build() { 1088 return builder.build(); 1089 } 1090 1091 /** 1092 * Immutable value which is accessed frequently, provide direct access to it. 1093 */ 1094 protected String getDocumentId() { 1095 return originalDocument.getDocumentId(); 1096 } 1097 1098 /** 1099 * Immutable value which is accessed frequently, provide direct access to it. 1100 */ 1101 protected DateTime getDateCreated() { 1102 return originalDocument.getDateCreated(); 1103 } 1104 1105 protected String getApplicationDocumentId() { 1106 return builder.getApplicationDocumentId(); 1107 } 1108 1109 protected void setApplicationDocumentId(String applicationDocumentId) { 1110 if ( !StringUtils.equals(applicationDocumentId, builder.getApplicationDocumentId() ) ) { 1111 builder.setApplicationDocumentId(applicationDocumentId); 1112 dirty = true; 1113 addDirtyField("applicationDocumentId"); 1114 } 1115 } 1116 1117 protected String getTitle() { 1118 return builder.getTitle(); 1119 } 1120 1121 protected void setTitle(String title) { 1122 if ( !StringUtils.equals(title, builder.getTitle() ) ) { 1123 builder.setTitle(title); 1124 dirty = true; 1125 addDirtyField("title"); 1126 } 1127 } 1128 1129 protected String getApplicationDocumentStatus() { 1130 return builder.getApplicationDocumentStatus(); 1131 } 1132 1133 protected void setApplicationDocumentStatus(String applicationDocumentStatus) { 1134 if ( !StringUtils.equals(applicationDocumentStatus, builder.getApplicationDocumentStatus() ) ) { 1135 builder.setApplicationDocumentStatus(applicationDocumentStatus); 1136 dirty = true; 1137 addDirtyField("applicationDocumentStatus"); 1138 } 1139 } 1140 1141 protected void setVariable(String name, String value) { 1142 if ( !StringUtils.equals(value, builder.getVariableValue(name) ) ) { 1143 builder.setVariable(name, value); 1144 dirty = true; 1145 addDirtyField("var[" + name + "]"); 1146 } 1147 } 1148 1149 protected String getVariableValue(String name) { 1150 return builder.getVariableValue(name); 1151 } 1152 1153 boolean isDirty() { 1154 return dirty; 1155 } 1156 1157 void addDirtyField(String field) { 1158 builder.addDirtyField(field); 1159 } 1160 1161 } 1162 1163}