View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.impl.document;
17  
18  import java.io.Serializable;
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.Date;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.joda.time.DateTime;
29  import org.kuali.rice.kew.api.KewApiConstants;
30  import org.kuali.rice.kew.api.KewApiServiceLocator;
31  import org.kuali.rice.kew.api.action.ActionRequest;
32  import org.kuali.rice.kew.api.action.ActionRequestType;
33  import org.kuali.rice.kew.api.action.ActionTaken;
34  import org.kuali.rice.kew.api.action.ActionType;
35  import org.kuali.rice.kew.api.action.AdHocRevoke;
36  import org.kuali.rice.kew.api.action.AdHocToGroup;
37  import org.kuali.rice.kew.api.action.AdHocToPrincipal;
38  import org.kuali.rice.kew.api.action.DocumentActionParameters;
39  import org.kuali.rice.kew.api.action.DocumentActionResult;
40  import org.kuali.rice.kew.api.action.MovePoint;
41  import org.kuali.rice.kew.api.action.RequestedActions;
42  import org.kuali.rice.kew.api.action.ReturnPoint;
43  import org.kuali.rice.kew.api.action.ValidActions;
44  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
45  import org.kuali.rice.kew.api.document.Document;
46  import org.kuali.rice.kew.api.document.DocumentContent;
47  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
48  import org.kuali.rice.kew.api.document.DocumentDetail;
49  import org.kuali.rice.kew.api.document.DocumentStatus;
50  import org.kuali.rice.kew.api.document.DocumentUpdate;
51  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
52  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeValidationError;
53  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
54  import org.kuali.rice.kew.api.document.WorkflowDocumentService;
55  
56  /**
57   * TODO ..
58   * 
59   * TODO - it is intended that operations against document data on this are only "flushed" when an
60   * action is performed...
61   * 
62   * <p>
63   * This class is *not* thread safe.
64   * 
65   */
66  public class WorkflowDocumentImpl implements Serializable, WorkflowDocumentPrototype {
67  
68      private static final long serialVersionUID = -3672966990721719088L;
69  
70      private String principalId;
71      private ModifiableDocument modifiableDocument;
72      private ModifiableDocumentContent modifiableDocumentContent;
73      private ValidActions validActions;
74      private RequestedActions requestedActions;
75  
76      private boolean documentDeleted = false;
77  
78      private transient WorkflowDocumentActionsService workflowDocumentActionsService;
79      private transient WorkflowDocumentService workflowDocumentService;
80  
81      public void init(String principalId, Document document) {
82          if (StringUtils.isBlank("principalId")) {
83              throw new IllegalArgumentException("principalId was null or blank");
84          }
85          if (document == null) {
86              throw new IllegalArgumentException("document was null");
87          }
88          this.principalId = principalId;
89          this.modifiableDocument = new ModifiableDocument(document);
90          this.modifiableDocumentContent = null;
91          this.validActions = null;
92          this.requestedActions = null;
93      }
94  
95      public WorkflowDocumentActionsService getWorkflowDocumentActionsService() {
96          if (workflowDocumentActionsService == null) {
97              workflowDocumentActionsService = KewApiServiceLocator.getWorkflowDocumentActionsService();
98          }
99          return workflowDocumentActionsService;
100     }
101 
102     public void setWorkflowDocumentActionsService(WorkflowDocumentActionsService workflowDocumentActionsService) {
103         this.workflowDocumentActionsService = workflowDocumentActionsService;
104     }
105 
106     public WorkflowDocumentService getWorkflowDocumentService() {
107         if (workflowDocumentService == null) {
108             workflowDocumentService = KewApiServiceLocator.getWorkflowDocumentService();
109         }
110         return workflowDocumentService;
111     }
112 
113     public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
114         this.workflowDocumentService = workflowDocumentService;
115     }
116 
117     protected ModifiableDocument getModifiableDocument() {
118         return modifiableDocument;
119     }
120 
121     protected ModifiableDocumentContent getModifiableDocumentContent() {
122         if (this.modifiableDocumentContent == null) {
123             DocumentContent documentContent = getWorkflowDocumentService().getDocumentContent(getDocumentId());
124             if (documentContent == null) {
125                 throw new IllegalStateException("Failed to load document content for documentId: " + getDocumentId());
126             }
127             this.modifiableDocumentContent = new ModifiableDocumentContent(documentContent);
128         }
129         return this.modifiableDocumentContent;
130     }
131 
132     @Override
133     public String getDocumentId() {
134         if (documentDeleted) {
135             throw new IllegalStateException("Document has been deleted.");
136         }
137         return getModifiableDocument().getDocumentId();
138     }
139 
140     @Override
141     public Document getDocument() {
142         return getModifiableDocument().getDocument();
143     }
144 
145     @Override
146     public DocumentContent getDocumentContent() {
147         return getModifiableDocumentContent().getDocumentContent();
148     }
149 
150     @Override
151     public String getApplicationContent() {
152         return getDocumentContent().getApplicationContent();
153     }
154 
155     @Override
156     public void setApplicationContent(String applicationContent) {
157         getModifiableDocumentContent().setApplicationContent(applicationContent);
158     }
159     
160     @Override
161     public void setAttributeContent(String attributeContent) {
162         getModifiableDocumentContent().setAttributeContent(attributeContent);
163     }
164 
165     @Override
166     public void clearAttributeContent() {
167         getModifiableDocumentContent().setAttributeContent("");
168     }
169 
170     @Override
171     public String getAttributeContent() {
172         return getDocumentContent().getAttributeContent();
173     }
174 
175     @Override
176     public void addAttributeDefinition(WorkflowAttributeDefinition attributeDefinition) {
177         getModifiableDocumentContent().addAttributeDefinition(attributeDefinition);
178     }
179 
180     @Override
181     public void removeAttributeDefinition(WorkflowAttributeDefinition attributeDefinition) {
182         getModifiableDocumentContent().removeAttributeDefinition(attributeDefinition);
183     }
184 
185     @Override
186     public void clearAttributeDefinitions() {
187         getAttributeDefinitions().clear();
188     }
189 
190     @Override
191     public List<WorkflowAttributeDefinition> getAttributeDefinitions() {
192         return getModifiableDocumentContent().getAttributeDefinitions();
193     }
194 
195     @Override
196     public void setSearchableContent(String searchableContent) {
197         getModifiableDocumentContent().setSearchableContent(searchableContent);
198     }
199     
200     @Override
201     public void addSearchableDefinition(WorkflowAttributeDefinition searchableDefinition) {
202         getModifiableDocumentContent().addSearchableDefinition(searchableDefinition);
203     }
204 
205     @Override
206     public void removeSearchableDefinition(WorkflowAttributeDefinition searchableDefinition) {
207         getModifiableDocumentContent().removeSearchableDefinition(searchableDefinition);
208     }
209 
210     @Override
211     public void clearSearchableDefinitions() {
212         getSearchableDefinitions().clear();
213     }
214 
215     @Override
216     public void clearSearchableContent() {
217         getModifiableDocumentContent().setSearchableContent("");
218     }
219 
220     @Override
221     public List<WorkflowAttributeDefinition> getSearchableDefinitions() {
222         return getModifiableDocumentContent().getSearchableDefinitions();
223     }
224 
225     @Override
226     public List<WorkflowAttributeValidationError> validateAttributeDefinition(
227             WorkflowAttributeDefinition attributeDefinition) {
228         return getWorkflowDocumentActionsService().validateWorkflowAttributeDefinition(attributeDefinition);
229     }
230 
231     @Override
232     public List<ActionRequest> getRootActionRequests() {
233         return getWorkflowDocumentService().getRootActionRequests(getDocumentId());
234     }
235 
236     @Override
237     public List<ActionTaken> getActionsTaken() {
238         return getWorkflowDocumentService().getActionsTaken(getDocumentId());
239     }
240 
241     @Override
242     public void setApplicationDocumentId(String applicationDocumentId) {
243         getModifiableDocument().setApplicationDocumentId(applicationDocumentId);
244     }
245 
246     @Override
247     public String getApplicationDocumentId() {
248         return getModifiableDocument().getApplicationDocumentId();
249     }
250 
251     @Override
252     public DateTime getDateCreated() {
253         return getModifiableDocument().getDateCreated();
254     }
255 
256     @Override
257     public String getTitle() {
258         return getModifiableDocument().getTitle();
259     }
260 
261     @Override
262     public ValidActions getValidActions() {
263         if (validActions == null) {
264             validActions = getWorkflowDocumentActionsService().determineValidActions(getDocumentId(), getPrincipalId());
265         }
266         return validActions;
267     }
268 
269     @Override
270     public RequestedActions getRequestedActions() {
271         if (requestedActions == null) {
272             requestedActions = getWorkflowDocumentActionsService().determineRequestedActions(getDocumentId(),
273                     getPrincipalId());
274         }
275         return requestedActions;
276     }
277 
278     protected DocumentUpdate getDocumentUpdateIfDirty() {
279         if (getModifiableDocument().isDirty()) {
280             return getModifiableDocument().build();
281         }
282         return null;
283     }
284 
285     protected DocumentContentUpdate getDocumentContentUpdateIfDirty() {
286         if (getModifiableDocumentContent().isDirty()) {
287             return getModifiableDocumentContent().build();
288         }
289         return null;
290     }
291 
292     protected void resetStateAfterAction(DocumentActionResult response) {
293         this.modifiableDocument = new ModifiableDocument(response.getDocument());
294         this.validActions = null;
295         if (response.getValidActions() != null) {
296             this.validActions = response.getValidActions();
297         }
298         this.requestedActions = null;
299         if (response.getRequestedActions() != null) {
300             this.requestedActions = response.getRequestedActions();
301         }
302         // regardless of whether modifiable document content is dirty, we null it out so it will be re-fetched next time it's needed
303         this.modifiableDocumentContent = null;
304     }
305 
306     @Override
307     public void saveDocument(String annotation) {
308         DocumentActionResult result = getWorkflowDocumentActionsService().save(
309                 constructDocumentActionParameters(annotation));
310         resetStateAfterAction(result);
311     }
312 
313     @Override
314     public void route(String annotation) {
315         DocumentActionResult result = getWorkflowDocumentActionsService().route(
316                 constructDocumentActionParameters(annotation));
317         resetStateAfterAction(result);
318     }
319 
320     @Override
321     public void disapprove(String annotation) {
322         DocumentActionResult result = getWorkflowDocumentActionsService().disapprove(
323                 constructDocumentActionParameters(annotation));
324         resetStateAfterAction(result);
325     }
326 
327     @Override
328     public void approve(String annotation) {
329         DocumentActionResult result = getWorkflowDocumentActionsService().approve(
330                 constructDocumentActionParameters(annotation));
331         resetStateAfterAction(result);
332     }
333 
334     @Override
335     public void cancel(String annotation) {
336         DocumentActionResult result = getWorkflowDocumentActionsService().cancel(
337                 constructDocumentActionParameters(annotation));
338         resetStateAfterAction(result);
339     }
340 
341     @Override
342     public void blanketApprove(String annotation) {
343         DocumentActionResult result = getWorkflowDocumentActionsService().blanketApprove(
344                 constructDocumentActionParameters(annotation));
345         resetStateAfterAction(result);
346     }
347 
348     @Override
349     public void blanketApprove(String annotation, String... nodeNames) {
350         if (nodeNames == null) {
351             throw new IllegalArgumentException("nodeNames was null");
352         }
353         Set<String> nodeNamesSet = new HashSet<String>(Arrays.asList(nodeNames));
354         DocumentActionResult result = getWorkflowDocumentActionsService().blanketApproveToNodes(
355                 constructDocumentActionParameters(annotation), nodeNamesSet);
356         resetStateAfterAction(result);
357     }
358 
359     @Override
360     public void saveDocumentData() {
361         DocumentActionResult result = getWorkflowDocumentActionsService().saveDocumentData(
362                 constructDocumentActionParameters(null));
363         resetStateAfterAction(result);
364     }
365 
366     @Override
367     public void setApplicationDocumentStatus(String applicationDocumentStatus) {
368         getModifiableDocument().setApplicationDocumentStatus(applicationDocumentStatus);
369     }
370 
371     @Override
372     public void acknowledge(String annotation) {
373         DocumentActionResult result = getWorkflowDocumentActionsService().acknowledge(
374                 constructDocumentActionParameters(annotation));
375         resetStateAfterAction(result);
376     }
377 
378     @Override
379     public void fyi(String annotation) {
380         DocumentActionResult result = getWorkflowDocumentActionsService().clearFyi(
381                 constructDocumentActionParameters(annotation));
382         resetStateAfterAction(result);
383     }
384 
385     @Override
386     public void fyi() {
387         fyi("");
388     }
389 
390     /**
391      * TODO - be sure to mention that once this document is deleted, this api effectively becomes
392      * "dead" when you try to execute any document operation
393      */
394     @Override
395     public void delete() {
396         getWorkflowDocumentActionsService().delete(getDocumentId(), principalId);
397         documentDeleted = true;
398     }
399 
400     @Override
401     public void refresh() {
402         Document document = getWorkflowDocumentService().getDocument(getDocumentId());
403         this.modifiableDocument = new ModifiableDocument(document);
404         this.validActions = null;
405         this.requestedActions = null;
406         this.modifiableDocumentContent = null;
407     }
408 
409     @Override
410     public void adHocToPrincipal(ActionRequestType actionRequested, String annotation, String targetPrincipalId,
411             String responsibilityDescription, boolean forceAction) {
412         adHocToPrincipal(actionRequested, null, annotation, targetPrincipalId, responsibilityDescription, forceAction);
413     }
414 
415     @Override
416     public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation,
417             String targetPrincipalId, String responsibilityDescription, boolean forceAction) {
418         adHocToPrincipal(actionRequested, nodeName, annotation, targetPrincipalId, responsibilityDescription,
419                 forceAction, null);
420     }
421 
422     @Override
423     public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation,
424             String targetPrincipalId, String responsibilityDescription, boolean forceAction, String requestLabel) {
425         AdHocToPrincipal.Builder builder = AdHocToPrincipal.Builder
426                 .create(actionRequested, nodeName, targetPrincipalId);
427         builder.setResponsibilityDescription(responsibilityDescription);
428         builder.setForceAction(forceAction);
429         builder.setRequestLabel(requestLabel);
430         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal(
431                 constructDocumentActionParameters(annotation), builder.build());
432         resetStateAfterAction(result);
433     }
434 
435     @Override
436     public void adHocToPrincipal(AdHocToPrincipal adHocToPrincipal, String annotation) {
437         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal(
438                 constructDocumentActionParameters(annotation), adHocToPrincipal);
439         resetStateAfterAction(result);
440     }
441 
442     @Override
443     public void adHocToGroup(ActionRequestType actionRequested, String annotation, String targetGroupId,
444             String responsibilityDescription, boolean forceAction) {
445         adHocToGroup(actionRequested, null, annotation, targetGroupId, responsibilityDescription, forceAction);
446     }
447 
448     @Override
449     public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation,
450             String targetGroupId, String responsibilityDescription, boolean forceAction) {
451         adHocToGroup(actionRequested, nodeName, annotation, targetGroupId, responsibilityDescription, forceAction, null);
452     }
453 
454     @Override
455     public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation,
456             String targetGroupId, String responsibilityDescription, boolean forceAction, String requestLabel) {
457         AdHocToGroup.Builder builder = AdHocToGroup.Builder.create(actionRequested, nodeName, targetGroupId);
458         builder.setResponsibilityDescription(responsibilityDescription);
459         builder.setForceAction(forceAction);
460         builder.setRequestLabel(requestLabel);
461         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup(
462                 constructDocumentActionParameters(annotation), builder.build());
463         resetStateAfterAction(result);
464     }
465 
466     @Override
467     public void adHocToGroup(AdHocToGroup adHocToGroup, String annotation) {
468         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup(
469                 constructDocumentActionParameters(annotation), adHocToGroup);
470         resetStateAfterAction(result);
471     }
472 
473     @Override
474     public void revokeAdHocRequestById(String actionRequestId, String annotation) {
475         if (StringUtils.isBlank(actionRequestId)) {
476             throw new IllegalArgumentException("actionRequestId was null or blank");
477         }
478         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequestById(
479                 constructDocumentActionParameters(annotation), actionRequestId);
480         resetStateAfterAction(result);
481     }
482 
483     @Override
484     public void revokeAdHocRequests(AdHocRevoke revoke, String annotation) {
485         if (revoke == null) {
486             throw new IllegalArgumentException("revokeFromPrincipal was null");
487         }
488         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequests(
489                 constructDocumentActionParameters(annotation), revoke);
490         resetStateAfterAction(result);
491     }
492 
493     @Override
494     public void revokeAllAdHocRequests(String annotation) {
495         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAllAdHocRequests(
496                 constructDocumentActionParameters(annotation));
497         resetStateAfterAction(result);
498     }
499 
500     @Override
501     public void setTitle(String title) {
502         getModifiableDocument().setTitle(title);
503     }
504 
505     @Override
506     public String getDocumentTypeName() {
507         return getDocument().getDocumentTypeName();
508     }
509 
510     @Override
511     public boolean isCompletionRequested() {
512         return getRequestedActions().isCompleteRequested();
513     }
514 
515     @Override
516     public boolean isApprovalRequested() {
517         return getRequestedActions().isApproveRequested();
518     }
519 
520     @Override
521     public boolean isAcknowledgeRequested() {
522         return getRequestedActions().isAcknowledgeRequested();
523     }
524 
525     @Override
526     public boolean isFYIRequested() {
527         return getRequestedActions().isFyiRequested();
528     }
529 
530     @Override
531     public boolean isBlanketApproveCapable() {
532         return isValidAction(ActionType.BLANKET_APPROVE)
533                 && (isCompletionRequested() || isApprovalRequested() || isInitiated());
534     }
535 
536     @Override
537     public boolean isRouteCapable() {
538         return isValidAction(ActionType.ROUTE);
539     }
540 
541     @Override
542     public boolean isValidAction(ActionType actionType) {
543         if (actionType == null) {
544             throw new IllegalArgumentException("actionType was null");
545         }
546         return getValidActions().getValidActions().contains(actionType);
547     }
548 
549     @Override
550     public void superUserBlanketApprove(String annotation) {
551         DocumentActionResult result = getWorkflowDocumentActionsService().superUserBlanketApprove(
552                 constructDocumentActionParameters(annotation), true);
553         resetStateAfterAction(result);
554     }
555 
556     @Override
557     public void superUserNodeApprove(String nodeName, String annotation) {
558         DocumentActionResult result = getWorkflowDocumentActionsService().superUserNodeApprove(
559                 constructDocumentActionParameters(annotation), true, nodeName);
560         resetStateAfterAction(result);
561     }
562 
563     @Override
564     public void superUserTakeRequestedAction(String actionRequestId, String annotation) {
565         DocumentActionResult result = getWorkflowDocumentActionsService().superUserTakeRequestedAction(
566                 constructDocumentActionParameters(annotation), true, actionRequestId);
567         resetStateAfterAction(result);
568     }
569 
570     @Override
571     public void superUserDisapprove(String annotation) {
572         DocumentActionResult result = getWorkflowDocumentActionsService().superUserDisapprove(
573                 constructDocumentActionParameters(annotation), true);
574         resetStateAfterAction(result);
575     }
576 
577     @Override
578     public void superUserCancel(String annotation) {
579         DocumentActionResult result = getWorkflowDocumentActionsService().superUserCancel(
580                 constructDocumentActionParameters(annotation), true);
581         resetStateAfterAction(result);
582     }
583 
584     @Override
585     public void superUserReturnToPreviousNode(ReturnPoint returnPoint, String annotation) {
586         DocumentActionResult result = getWorkflowDocumentActionsService().superUserReturnToPreviousNode(
587                 constructDocumentActionParameters(annotation), true, returnPoint);
588         resetStateAfterAction(result);
589     }
590 
591     @Override
592     public void complete(String annotation) {
593         DocumentActionResult result = getWorkflowDocumentActionsService().complete(
594                 constructDocumentActionParameters(annotation));
595         resetStateAfterAction(result);
596     }
597 
598     @Override
599     public void logAnnotation(String annotation) {
600         getWorkflowDocumentActionsService().logAnnotation(getDocumentId(), principalId, annotation);
601     }
602 
603     @Override
604     public DocumentStatus getStatus() {
605         return getDocument().getStatus();
606     }
607 
608     @Override
609     public boolean checkStatus(DocumentStatus status) {
610         if (status == null) {
611             throw new IllegalArgumentException("status was null");
612         }
613         return status == getStatus();
614     }
615 
616     /**
617      * Indicates if the document is in the initiated state or not.
618      * 
619      * @return true if in the specified state
620      */
621     @Override
622     public boolean isInitiated() {
623         return checkStatus(DocumentStatus.INITIATED);
624     }
625 
626     /**
627      * Indicates if the document is in the saved state or not.
628      * 
629      * @return true if in the specified state
630      */
631     @Override
632     public boolean isSaved() {
633         return checkStatus(DocumentStatus.SAVED);
634     }
635 
636     /**
637      * Indicates if the document is in the enroute state or not.
638      * 
639      * @return true if in the specified state
640      */
641     @Override
642     public boolean isEnroute() {
643         return checkStatus(DocumentStatus.ENROUTE);
644     }
645 
646     /**
647      * Indicates if the document is in the exception state or not.
648      * 
649      * @return true if in the specified state
650      */
651     @Override
652     public boolean isException() {
653         return checkStatus(DocumentStatus.EXCEPTION);
654     }
655 
656     /**
657      * Indicates if the document is in the canceled state or not.
658      * 
659      * @return true if in the specified state
660      */
661     @Override
662     public boolean isCanceled() {
663         return checkStatus(DocumentStatus.CANCELED);
664     }
665 
666     /**
667      * Indicates if the document is in the disapproved state or not.
668      * 
669      * @return true if in the specified state
670      */
671     @Override
672     public boolean isDisapproved() {
673         return checkStatus(DocumentStatus.DISAPPROVED);
674     }
675 
676     /**
677      * Indicates if the document is in the Processed or Finalized state.
678      * 
679      * @return true if in the specified state
680      */
681     @Override
682     public boolean isApproved() {
683         return isProcessed() || isFinal();
684     }
685 
686     /**
687      * Indicates if the document is in the processed state or not.
688      * 
689      * @return true if in the specified state
690      */
691     @Override
692     public boolean isProcessed() {
693         return checkStatus(DocumentStatus.PROCESSED);
694     }
695 
696     /**
697      * Indicates if the document is in the final state or not.
698      * 
699      * @return true if in the specified state
700      */
701     @Override
702     public boolean isFinal() {
703         return checkStatus(DocumentStatus.FINAL);
704     }
705 
706     /**
707      * Returns the principalId with which this WorkflowDocument was constructed
708      * 
709      * @return the principalId with which this WorkflowDocument was constructed
710      */
711     @Override
712     public String getPrincipalId() {
713         return principalId;
714     }
715 
716     @Override
717     public void switchPrincipal(String principalId) {
718         if (StringUtils.isBlank(this.principalId)) {
719             throw new IllegalArgumentException("principalId was null or blank");
720         }
721         this.principalId = principalId;
722         this.validActions = null;
723         this.requestedActions = null;
724     }
725 
726     @Override
727     public void takeGroupAuthority(String annotation, String groupId) {
728         DocumentActionResult result = getWorkflowDocumentActionsService().takeGroupAuthority(
729                 constructDocumentActionParameters(annotation), groupId);
730         resetStateAfterAction(result);
731     }
732 
733     @Override
734     public void releaseGroupAuthority(String annotation, String groupId) {
735         DocumentActionResult result = getWorkflowDocumentActionsService().releaseGroupAuthority(
736                 constructDocumentActionParameters(annotation), groupId);
737         resetStateAfterAction(result);
738     }
739 
740     @Override
741     public Set<String> getNodeNames() {
742         List<RouteNodeInstance> activeNodeInstances = getActiveRouteNodeInstances();
743         Set<String> nodeNames = new HashSet<String>(activeNodeInstances.size());
744         for (RouteNodeInstance routeNodeInstance : activeNodeInstances) {
745             nodeNames.add(routeNodeInstance.getName());
746         }
747         return Collections.unmodifiableSet(nodeNames);
748     }
749 
750     public Set<String> getCurrentNodeNames() {
751         List<RouteNodeInstance> currentNodeInstances = getCurrentRouteNodeInstances();
752         Set<String> nodeNames = new HashSet<String>(currentNodeInstances.size());
753         for (RouteNodeInstance routeNodeInstance : currentNodeInstances) {
754             nodeNames.add(routeNodeInstance.getName());
755         }
756         return Collections.unmodifiableSet(nodeNames);
757     }
758 
759     @Override
760     public void returnToPreviousNode(String nodeName, String annotation) {
761         if (nodeName == null) {
762             throw new IllegalArgumentException("nodeName was null");
763         }
764         returnToPreviousNode(ReturnPoint.create(nodeName), annotation);
765     }
766 
767     @Override
768     public void returnToPreviousNode(ReturnPoint returnPoint, String annotation) {
769         if (returnPoint == null) {
770             throw new IllegalArgumentException("returnPoint was null");
771         }
772         DocumentActionResult result = getWorkflowDocumentActionsService().returnToPreviousNode(
773                 constructDocumentActionParameters(annotation), returnPoint);
774         resetStateAfterAction(result);
775     }
776 
777     @Override
778     public void move(MovePoint movePoint, String annotation) {
779         if (movePoint == null) {
780             throw new IllegalArgumentException("movePoint was null");
781         }
782         DocumentActionResult result = getWorkflowDocumentActionsService().move(
783                 constructDocumentActionParameters(annotation), movePoint);
784         resetStateAfterAction(result);
785     }
786 
787     @Override
788     public List<RouteNodeInstance> getActiveRouteNodeInstances() {
789         return getWorkflowDocumentService().getActiveRouteNodeInstances(getDocumentId());
790     }
791 
792     @Override
793     public List<RouteNodeInstance> getCurrentRouteNodeInstances() {
794         return getWorkflowDocumentService().getCurrentRouteNodeInstances(getDocumentId());
795     }
796 
797     @Override
798     public List<RouteNodeInstance> getRouteNodeInstances() {
799         return getWorkflowDocumentService().getRouteNodeInstances(getDocumentId());
800     }
801 
802     @Override
803     public List<String> getPreviousNodeNames() {
804         return getWorkflowDocumentService().getPreviousRouteNodeNames(getDocumentId());
805     }
806 
807     @Override
808     public DocumentDetail getDocumentDetail() {
809         return getWorkflowDocumentService().getDocumentDetail(getDocumentId());
810     }
811 
812     @Override
813     public void updateDocumentContent(DocumentContentUpdate documentContentUpdate) {
814         if (documentContentUpdate == null) {
815             throw new IllegalArgumentException("documentContentUpdate was null.");
816         }
817         getModifiableDocumentContent().setDocumentContentUpdate(documentContentUpdate);
818     }
819 
820     @Override
821     public void placeInExceptionRouting(String annotation) {
822         DocumentActionResult result = getWorkflowDocumentActionsService().placeInExceptionRouting(
823                 constructDocumentActionParameters(annotation));
824         resetStateAfterAction(result);
825     }
826 
827     @Override
828     public void setVariable(String name, String value) {
829         getModifiableDocument().setVariable(name, value);
830     }
831 
832     @Override
833     public String getVariableValue(String name) {
834         return getModifiableDocument().getVariableValue(name);
835     }
836 
837     @Override
838     public void setReceiveFutureRequests() {
839         setVariable(getFutureRequestsKey(principalId), getReceiveFutureRequestsValue());
840     }
841 
842     @Override
843     public void setDoNotReceiveFutureRequests() {
844         this.setVariable(getFutureRequestsKey(principalId), getDoNotReceiveFutureRequestsValue());
845     }
846 
847     @Override
848     public void setClearFutureRequests() {
849         this.setVariable(getFutureRequestsKey(principalId), getClearFutureRequestsValue());
850     }
851 
852     protected String getFutureRequestsKey(String principalId) {
853         return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + ","
854                 + new Date().toString() + ", " + Math.random();
855     }
856 
857     @Override
858     public String getReceiveFutureRequestsValue() {
859         return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
860     }
861 
862     @Override
863     public String getDoNotReceiveFutureRequestsValue() {
864         return KewApiConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
865     }
866 
867     @Override
868     public String getClearFutureRequestsValue() {
869         return KewApiConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
870     }
871 
872     protected DocumentActionParameters constructDocumentActionParameters(String annotation) {
873         DocumentActionParameters.Builder builder = DocumentActionParameters.Builder.create(getDocumentId(),
874                 getPrincipalId());
875         builder.setAnnotation(annotation);
876         builder.setDocumentUpdate(getDocumentUpdateIfDirty());
877         builder.setDocumentContentUpdate(getDocumentContentUpdateIfDirty());
878         return builder.build();
879     }
880     
881     @Override
882     public DateTime getDateLastModified() {
883         return getDocument().getDateLastModified();
884     }
885 
886     @Override
887     public DateTime getDateApproved() {
888         return getDocument().getDateApproved();
889     }
890 
891     @Override
892     public DateTime getDateFinalized() {
893         return getDocument().getDateFinalized();
894     }
895 
896     @Override
897     public String getInitiatorPrincipalId() {
898         return getDocument().getInitiatorPrincipalId();
899     }
900 
901     @Override
902     public String getRoutedByPrincipalId() {
903         return getDocument().getRoutedByPrincipalId();
904     }
905 
906     @Override
907     public String getDocumentTypeId() {
908         return getDocument().getDocumentTypeId();
909     }
910 
911     @Override
912     public String getDocumentHandlerUrl() {
913         return getDocument().getDocumentHandlerUrl();
914     }
915 
916     @Override
917     public String getApplicationDocumentStatus() {
918         return getDocument().getApplicationDocumentStatus();
919     }
920 
921     @Override
922     public DateTime getApplicationDocumentStatusDate() {
923         return getDocument().getApplicationDocumentStatusDate();
924     }
925 
926     @Override
927     public Map<String, String> getVariables() {
928         return getDocument().getVariables();
929     }
930 
931     protected static class ModifiableDocumentContent implements Serializable {
932 
933         private static final long serialVersionUID = -4458431160327214042L;
934 
935         private boolean dirty;
936         private DocumentContent originalDocumentContent;
937         private DocumentContentUpdate.Builder builder;
938 
939         protected ModifiableDocumentContent(DocumentContent documentContent) {
940             this.dirty = false;
941             this.originalDocumentContent = documentContent;
942             this.builder = DocumentContentUpdate.Builder.create(documentContent);
943         }
944 
945         protected DocumentContent getDocumentContent() {
946             if (!dirty) {
947                 return originalDocumentContent;
948             }
949             DocumentContent.Builder documentContentBuilder = DocumentContent.Builder.create(originalDocumentContent);
950             documentContentBuilder.setApplicationContent(builder.getApplicationContent());
951             documentContentBuilder.setAttributeContent(builder.getApplicationContent());
952             documentContentBuilder.setSearchableContent(builder.getSearchableContent());
953             return documentContentBuilder.build();
954         }
955 
956         protected DocumentContentUpdate build() {
957             return builder.build();
958         }
959 
960         protected void setDocumentContentUpdate(DocumentContentUpdate update) {
961             this.builder = DocumentContentUpdate.Builder.create(update);
962             this.dirty = true;
963         }
964 
965         protected void addAttributeDefinition(WorkflowAttributeDefinition definition) {
966             builder.getAttributeDefinitions().add(definition);
967             dirty = true;
968         }
969 
970         protected void removeAttributeDefinition(WorkflowAttributeDefinition definition) {
971             builder.getAttributeDefinitions().remove(definition);
972             dirty = true;
973         }
974 
975         protected List<WorkflowAttributeDefinition> getAttributeDefinitions() {
976             return builder.getAttributeDefinitions();
977         }
978 
979         protected void addSearchableDefinition(WorkflowAttributeDefinition definition) {
980             builder.getSearchableDefinitions().add(definition);
981             dirty = true;
982         }
983 
984         protected void removeSearchableDefinition(WorkflowAttributeDefinition definition) {
985             builder.getSearchableDefinitions().remove(definition);
986             dirty = true;
987         }
988 
989         protected List<WorkflowAttributeDefinition> getSearchableDefinitions() {
990             return builder.getAttributeDefinitions();
991         }
992 
993         protected void setApplicationContent(String applicationContent) {
994             builder.setApplicationContent(applicationContent);
995             dirty = true;
996         }
997 
998         protected void setAttributeContent(String attributeContent) {
999             builder.setAttributeContent(attributeContent);
1000             dirty = true;
1001         }
1002 
1003         public void setAttributeDefinitions(List<WorkflowAttributeDefinition> attributeDefinitions) {
1004             builder.setAttributeDefinitions(attributeDefinitions);
1005             dirty = true;
1006         }
1007 
1008         public void setSearchableContent(String searchableContent) {
1009             builder.setSearchableContent(searchableContent);
1010             dirty = true;
1011         }
1012 
1013         public void setSearchableDefinitions(List<WorkflowAttributeDefinition> searchableDefinitions) {
1014             builder.setSearchableDefinitions(searchableDefinitions);
1015             dirty = true;
1016         }
1017 
1018         boolean isDirty() {
1019             return dirty;
1020         }
1021 
1022     }
1023 
1024     protected static class ModifiableDocument implements Serializable {
1025 
1026         private static final long serialVersionUID = -3234793238863410378L;
1027 
1028         private boolean dirty;
1029         private Document originalDocument;
1030         private DocumentUpdate.Builder builder;
1031 
1032         protected ModifiableDocument(Document document) {
1033             this.dirty = false;
1034             this.originalDocument = document;
1035             this.builder = DocumentUpdate.Builder.create(document);
1036         }
1037 
1038         protected Document getDocument() {
1039             if (!dirty) {
1040                 return originalDocument;
1041             }
1042             Document.Builder documentBuilder = Document.Builder.create(originalDocument);
1043             documentBuilder.setApplicationDocumentId(builder.getApplicationDocumentId());
1044             documentBuilder.setTitle(builder.getTitle());
1045             return documentBuilder.build();
1046         }
1047 
1048         protected DocumentUpdate build() {
1049             return builder.build();
1050         }
1051 
1052         /**
1053          * Immutable value which is accessed frequently, provide direct access to it.
1054          */
1055         protected String getDocumentId() {
1056             return originalDocument.getDocumentId();
1057         }
1058 
1059         /**
1060          * Immutable value which is accessed frequently, provide direct access to it.
1061          */
1062         protected DateTime getDateCreated() {
1063             return originalDocument.getDateCreated();
1064         }
1065 
1066         protected String getApplicationDocumentId() {
1067             return builder.getApplicationDocumentId();
1068         }
1069 
1070         protected void setApplicationDocumentId(String applicationDocumentId) {
1071             builder.setApplicationDocumentId(applicationDocumentId);
1072             dirty = true;
1073         }
1074 
1075         protected String getTitle() {
1076             return builder.getTitle();
1077         }
1078 
1079         protected void setTitle(String title) {
1080             builder.setTitle(title);
1081             dirty = true;
1082         }
1083 
1084         protected String getApplicationDocumentStatus() {
1085             return builder.getApplicationDocumentStatus();
1086         }
1087 
1088         protected void setApplicationDocumentStatus(String applicationDocumentStatus) {
1089             builder.setApplicationDocumentStatus(applicationDocumentStatus);
1090             dirty = true;
1091         }
1092 
1093         protected void setVariable(String name, String value) {
1094             builder.setVariable(name, value);
1095             dirty = true;
1096         }
1097 
1098         protected String getVariableValue(String name) {
1099             return builder.getVariableValue(name);
1100         }
1101 
1102         boolean isDirty() {
1103             return dirty;
1104         }
1105 
1106     }
1107 
1108 }