View Javadoc

1   /**
2    * Copyright 2005-2012 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.core.api.uif.RemotableAttributeErrorContract;
30  import org.kuali.rice.kew.api.KewApiConstants;
31  import org.kuali.rice.kew.api.KewApiServiceLocator;
32  import org.kuali.rice.kew.api.action.ActionRequest;
33  import org.kuali.rice.kew.api.action.ActionRequestType;
34  import org.kuali.rice.kew.api.action.ActionTaken;
35  import org.kuali.rice.kew.api.action.ActionType;
36  import org.kuali.rice.kew.api.action.AdHocRevoke;
37  import org.kuali.rice.kew.api.action.AdHocToGroup;
38  import org.kuali.rice.kew.api.action.AdHocToPrincipal;
39  import org.kuali.rice.kew.api.action.DocumentActionParameters;
40  import org.kuali.rice.kew.api.action.DocumentActionResult;
41  import org.kuali.rice.kew.api.action.MovePoint;
42  import org.kuali.rice.kew.api.action.RequestedActions;
43  import org.kuali.rice.kew.api.action.ReturnPoint;
44  import org.kuali.rice.kew.api.action.ValidActions;
45  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
46  import org.kuali.rice.kew.api.document.Document;
47  import org.kuali.rice.kew.api.document.DocumentContent;
48  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
49  import org.kuali.rice.kew.api.document.DocumentDetail;
50  import org.kuali.rice.kew.api.document.DocumentStatus;
51  import org.kuali.rice.kew.api.document.DocumentUpdate;
52  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
53  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
54  import org.kuali.rice.kew.api.document.WorkflowDocumentService;
55  
56  /**
57   * The implementation of {@link org.kuali.rice.kew.api.WorkflowDocument}.  Implements {@link WorkflowDocumentPrototype} to expose
58   * and initialization method used for construction.
59   * <p>NOTE: operations against document data on this are only "flushed" when an action is performed.</p>
60   * <p><b>This class is *not* thread safe.</b></p>
61   * @see org.kuali.rice.kew.api.WorkflowDocument
62   */
63  public class WorkflowDocumentImpl implements Serializable, WorkflowDocumentPrototype {
64  
65      private static final long serialVersionUID = -3672966990721719088L;
66  
67      /**
68       * The principal id under which all document actions will be performed.
69       */
70      private String principalId;
71      /**
72       * Stores local changes that need to be committed.
73       */
74      private ModifiableDocument modifiableDocument;
75      /**
76       * Stores local changes that need to be committed.
77       */
78      private ModifiableDocumentContent modifiableDocumentContent;
79      /**
80       * Local cache of valid document actions.
81       * @see #getValidActions()  
82       */
83      private ValidActions validActions;
84      /**
85       * Local cache of requested document actions.
86       * @see #getRequestedActions()
87       */
88      private RequestedActions requestedActions;
89      /**
90       * Flag that indicates whether the document has been deleted; if so the object is thereafter in an illegal state.
91       */
92      private boolean documentDeleted = false;
93  
94      private transient WorkflowDocumentActionsService workflowDocumentActionsService;
95      private transient WorkflowDocumentService workflowDocumentService;
96  
97      public void init(String principalId, Document document) {
98          if (StringUtils.isBlank("principalId")) {
99              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 blanketApprove(String annotation) {
359         DocumentActionResult result = getWorkflowDocumentActionsService().blanketApprove(
360                 constructDocumentActionParameters(annotation));
361         resetStateAfterAction(result);
362     }
363 
364     @Override
365     public void blanketApprove(String annotation, String... nodeNames) {
366         if (nodeNames == null) {
367             throw new IllegalArgumentException("nodeNames was null");
368         }
369         Set<String> nodeNamesSet = new HashSet<String>(Arrays.asList(nodeNames));
370         DocumentActionResult result = getWorkflowDocumentActionsService().blanketApproveToNodes(
371                 constructDocumentActionParameters(annotation), nodeNamesSet);
372         resetStateAfterAction(result);
373     }
374 
375     @Override
376     public void saveDocumentData() {
377         DocumentActionResult result = getWorkflowDocumentActionsService().saveDocumentData(
378                 constructDocumentActionParameters(null));
379         resetStateAfterAction(result);
380     }
381 
382     @Override
383     public void setApplicationDocumentStatus(String applicationDocumentStatus) {
384         getModifiableDocument().setApplicationDocumentStatus(applicationDocumentStatus);
385     }
386 
387     @Override
388     public void acknowledge(String annotation) {
389         DocumentActionResult result = getWorkflowDocumentActionsService().acknowledge(
390                 constructDocumentActionParameters(annotation));
391         resetStateAfterAction(result);
392     }
393 
394     @Override
395     public void fyi(String annotation) {
396         DocumentActionResult result = getWorkflowDocumentActionsService().clearFyi(
397                 constructDocumentActionParameters(annotation));
398         resetStateAfterAction(result);
399     }
400 
401     @Override
402     public void fyi() {
403         fyi("");
404     }
405 
406     @Override
407     public void delete() {
408         getWorkflowDocumentActionsService().delete(getDocumentId(), principalId);
409         documentDeleted = true;
410     }
411 
412     @Override
413     public void refresh() {
414         Document document = getWorkflowDocumentService().getDocument(getDocumentId());
415         this.modifiableDocument = new ModifiableDocument(document);
416         this.validActions = null;
417         this.requestedActions = null;
418         this.modifiableDocumentContent = null;
419     }
420 
421     @Override
422     public void adHocToPrincipal(ActionRequestType actionRequested, String annotation, String targetPrincipalId,
423             String responsibilityDescription, boolean forceAction) {
424         adHocToPrincipal(actionRequested, null, annotation, targetPrincipalId, responsibilityDescription, forceAction);
425     }
426 
427     @Override
428     public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation,
429             String targetPrincipalId, String responsibilityDescription, boolean forceAction) {
430         adHocToPrincipal(actionRequested, nodeName, annotation, targetPrincipalId, responsibilityDescription,
431                 forceAction, null);
432     }
433 
434     @Override
435     public void adHocToPrincipal(ActionRequestType actionRequested, String nodeName, String annotation,
436             String targetPrincipalId, String responsibilityDescription, boolean forceAction, String requestLabel) {
437         AdHocToPrincipal.Builder builder = AdHocToPrincipal.Builder
438                 .create(actionRequested, nodeName, targetPrincipalId);
439         builder.setResponsibilityDescription(responsibilityDescription);
440         builder.setForceAction(forceAction);
441         builder.setRequestLabel(requestLabel);
442         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal(
443                 constructDocumentActionParameters(annotation), builder.build());
444         resetStateAfterAction(result);
445     }
446 
447     @Override
448     public void adHocToPrincipal(AdHocToPrincipal adHocToPrincipal, String annotation) {
449         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToPrincipal(
450                 constructDocumentActionParameters(annotation), adHocToPrincipal);
451         resetStateAfterAction(result);
452     }
453 
454     @Override
455     public void adHocToGroup(ActionRequestType actionRequested, String annotation, String targetGroupId,
456             String responsibilityDescription, boolean forceAction) {
457         adHocToGroup(actionRequested, null, annotation, targetGroupId, responsibilityDescription, forceAction);
458     }
459 
460     @Override
461     public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation,
462             String targetGroupId, String responsibilityDescription, boolean forceAction) {
463         adHocToGroup(actionRequested, nodeName, annotation, targetGroupId, responsibilityDescription, forceAction, null);
464     }
465 
466     @Override
467     public void adHocToGroup(ActionRequestType actionRequested, String nodeName, String annotation,
468             String targetGroupId, String responsibilityDescription, boolean forceAction, String requestLabel) {
469         AdHocToGroup.Builder builder = AdHocToGroup.Builder.create(actionRequested, nodeName, targetGroupId);
470         builder.setResponsibilityDescription(responsibilityDescription);
471         builder.setForceAction(forceAction);
472         builder.setRequestLabel(requestLabel);
473         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup(
474                 constructDocumentActionParameters(annotation), builder.build());
475         resetStateAfterAction(result);
476     }
477 
478     @Override
479     public void adHocToGroup(AdHocToGroup adHocToGroup, String annotation) {
480         DocumentActionResult result = getWorkflowDocumentActionsService().adHocToGroup(
481                 constructDocumentActionParameters(annotation), adHocToGroup);
482         resetStateAfterAction(result);
483     }
484 
485     @Override
486     public void revokeAdHocRequestById(String actionRequestId, String annotation) {
487         if (StringUtils.isBlank(actionRequestId)) {
488             throw new IllegalArgumentException("actionRequestId was null or blank");
489         }
490         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequestById(
491                 constructDocumentActionParameters(annotation), actionRequestId);
492         resetStateAfterAction(result);
493     }
494 
495     @Override
496     public void revokeAdHocRequests(AdHocRevoke revoke, String annotation) {
497         if (revoke == null) {
498             throw new IllegalArgumentException("revokeFromPrincipal was null");
499         }
500         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAdHocRequests(
501                 constructDocumentActionParameters(annotation), revoke);
502         resetStateAfterAction(result);
503     }
504 
505     @Override
506     public void revokeAllAdHocRequests(String annotation) {
507         DocumentActionResult result = getWorkflowDocumentActionsService().revokeAllAdHocRequests(
508                 constructDocumentActionParameters(annotation));
509         resetStateAfterAction(result);
510     }
511 
512     @Override
513     public void setTitle(String title) {
514         getModifiableDocument().setTitle(title);
515     }
516 
517     @Override
518     public String getDocumentTypeName() {
519         return getDocument().getDocumentTypeName();
520     }
521 
522     @Override
523     public boolean isCompletionRequested() {
524         return getRequestedActions().isCompleteRequested();
525     }
526 
527     @Override
528     public boolean isApprovalRequested() {
529         return getRequestedActions().isApproveRequested();
530     }
531 
532     @Override
533     public boolean isAcknowledgeRequested() {
534         return getRequestedActions().isAcknowledgeRequested();
535     }
536 
537     @Override
538     public boolean isFYIRequested() {
539         return getRequestedActions().isFyiRequested();
540     }
541 
542     @Override
543     public boolean isBlanketApproveCapable() {
544         return isValidAction(ActionType.BLANKET_APPROVE)
545                 && (isCompletionRequested() || isApprovalRequested() || isInitiated());
546     }
547 
548     @Override
549     public boolean isRouteCapable() {
550         return isValidAction(ActionType.ROUTE);
551     }
552 
553     @Override
554     public boolean isValidAction(ActionType actionType) {
555         if (actionType == null) {
556             throw new IllegalArgumentException("actionType was null");
557         }
558         return getValidActions().getValidActions().contains(actionType);
559     }
560 
561     @Override
562     public void superUserBlanketApprove(String annotation) {
563         DocumentActionResult result = getWorkflowDocumentActionsService().superUserBlanketApprove(
564                 constructDocumentActionParameters(annotation), true);
565         resetStateAfterAction(result);
566     }
567 
568     @Override
569     public void superUserNodeApprove(String nodeName, String annotation) {
570         DocumentActionResult result = getWorkflowDocumentActionsService().superUserNodeApprove(
571                 constructDocumentActionParameters(annotation), true, nodeName);
572         resetStateAfterAction(result);
573     }
574 
575     @Override
576     public void superUserTakeRequestedAction(String actionRequestId, String annotation) {
577         DocumentActionResult result = getWorkflowDocumentActionsService().superUserTakeRequestedAction(
578                 constructDocumentActionParameters(annotation), true, actionRequestId);
579         resetStateAfterAction(result);
580     }
581 
582     @Override
583     public void superUserDisapprove(String annotation) {
584         DocumentActionResult result = getWorkflowDocumentActionsService().superUserDisapprove(
585                 constructDocumentActionParameters(annotation), true);
586         resetStateAfterAction(result);
587     }
588 
589     @Override
590     public void superUserCancel(String annotation) {
591         DocumentActionResult result = getWorkflowDocumentActionsService().superUserCancel(
592                 constructDocumentActionParameters(annotation), true);
593         resetStateAfterAction(result);
594     }
595 
596     @Override
597     public void superUserReturnToPreviousNode(ReturnPoint returnPoint, String annotation) {
598         DocumentActionResult result = getWorkflowDocumentActionsService().superUserReturnToPreviousNode(
599                 constructDocumentActionParameters(annotation), true, returnPoint);
600         resetStateAfterAction(result);
601     }
602 
603     @Override
604     public void complete(String annotation) {
605         DocumentActionResult result = getWorkflowDocumentActionsService().complete(
606                 constructDocumentActionParameters(annotation));
607         resetStateAfterAction(result);
608     }
609 
610     @Override
611     public void logAnnotation(String annotation) {
612         getWorkflowDocumentActionsService().logAnnotation(getDocumentId(), principalId, annotation);
613     }
614 
615     @Override
616     public DocumentStatus getStatus() {
617         return getDocument().getStatus();
618     }
619 
620     @Override
621     public boolean checkStatus(DocumentStatus status) {
622         if (status == null) {
623             throw new IllegalArgumentException("status was null");
624         }
625         return status == getStatus();
626     }
627 
628     /**
629      * Indicates if the document is in the initiated state or not.
630      * 
631      * @return true if in the specified state
632      */
633     @Override
634     public boolean isInitiated() {
635         return checkStatus(DocumentStatus.INITIATED);
636     }
637 
638     /**
639      * Indicates if the document is in the saved state or not.
640      * 
641      * @return true if in the specified state
642      */
643     @Override
644     public boolean isSaved() {
645         return checkStatus(DocumentStatus.SAVED);
646     }
647 
648     /**
649      * Indicates if the document is in the enroute state or not.
650      * 
651      * @return true if in the specified state
652      */
653     @Override
654     public boolean isEnroute() {
655         return checkStatus(DocumentStatus.ENROUTE);
656     }
657 
658     /**
659      * Indicates if the document is in the exception state or not.
660      * 
661      * @return true if in the specified state
662      */
663     @Override
664     public boolean isException() {
665         return checkStatus(DocumentStatus.EXCEPTION);
666     }
667 
668     /**
669      * Indicates if the document is in the canceled state or not.
670      * 
671      * @return true if in the specified state
672      */
673     @Override
674     public boolean isCanceled() {
675         return checkStatus(DocumentStatus.CANCELED);
676     }
677 
678     /**
679      * Indicates if the document is in the disapproved state or not.
680      * 
681      * @return true if in the specified state
682      */
683     @Override
684     public boolean isDisapproved() {
685         return checkStatus(DocumentStatus.DISAPPROVED);
686     }
687 
688     /**
689      * Indicates if the document is in the Processed or Finalized state.
690      * 
691      * @return true if in the specified state
692      */
693     @Override
694     public boolean isApproved() {
695         return isProcessed() || isFinal();
696     }
697 
698     /**
699      * Indicates if the document is in the processed state or not.
700      * 
701      * @return true if in the specified state
702      */
703     @Override
704     public boolean isProcessed() {
705         return checkStatus(DocumentStatus.PROCESSED);
706     }
707 
708     /**
709      * Indicates if the document is in the final state or not.
710      * 
711      * @return true if in the specified state
712      */
713     @Override
714     public boolean isFinal() {
715         return checkStatus(DocumentStatus.FINAL);
716     }
717 
718     /**
719      * Returns the principalId with which this WorkflowDocument was constructed
720      * 
721      * @return the principalId with which this WorkflowDocument was constructed
722      */
723     @Override
724     public String getPrincipalId() {
725         return principalId;
726     }
727 
728     @Override
729     public void switchPrincipal(String principalId) {
730         if (StringUtils.isBlank(this.principalId)) {
731             throw new IllegalArgumentException("principalId was null or blank");
732         }
733         this.principalId = principalId;
734         this.validActions = null;
735         this.requestedActions = null;
736     }
737 
738     @Override
739     public void takeGroupAuthority(String annotation, String groupId) {
740         DocumentActionResult result = getWorkflowDocumentActionsService().takeGroupAuthority(
741                 constructDocumentActionParameters(annotation), groupId);
742         resetStateAfterAction(result);
743     }
744 
745     @Override
746     public void releaseGroupAuthority(String annotation, String groupId) {
747         DocumentActionResult result = getWorkflowDocumentActionsService().releaseGroupAuthority(
748                 constructDocumentActionParameters(annotation), groupId);
749         resetStateAfterAction(result);
750     }
751 
752     @Override
753     public Set<String> getNodeNames() {
754         List<RouteNodeInstance> activeNodeInstances = getActiveRouteNodeInstances();
755         Set<String> nodeNames = new HashSet<String>(activeNodeInstances.size());
756         for (RouteNodeInstance routeNodeInstance : activeNodeInstances) {
757             nodeNames.add(routeNodeInstance.getName());
758         }
759         return Collections.unmodifiableSet(nodeNames);
760     }
761 
762     public Set<String> getCurrentNodeNames() {
763         List<RouteNodeInstance> currentNodeInstances = getCurrentRouteNodeInstances();
764         Set<String> nodeNames = new HashSet<String>(currentNodeInstances.size());
765         for (RouteNodeInstance routeNodeInstance : currentNodeInstances) {
766             nodeNames.add(routeNodeInstance.getName());
767         }
768         return Collections.unmodifiableSet(nodeNames);
769     }
770 
771     @Override
772     public void returnToPreviousNode(String nodeName, String annotation) {
773         if (nodeName == null) {
774             throw new IllegalArgumentException("nodeName was null");
775         }
776         returnToPreviousNode(ReturnPoint.create(nodeName), annotation);
777     }
778 
779     @Override
780     public void returnToPreviousNode(ReturnPoint returnPoint, String annotation) {
781         if (returnPoint == null) {
782             throw new IllegalArgumentException("returnPoint was null");
783         }
784         DocumentActionResult result = getWorkflowDocumentActionsService().returnToPreviousNode(
785                 constructDocumentActionParameters(annotation), returnPoint);
786         resetStateAfterAction(result);
787     }
788 
789     @Override
790     public void move(MovePoint movePoint, String annotation) {
791         if (movePoint == null) {
792             throw new IllegalArgumentException("movePoint was null");
793         }
794         DocumentActionResult result = getWorkflowDocumentActionsService().move(
795                 constructDocumentActionParameters(annotation), movePoint);
796         resetStateAfterAction(result);
797     }
798 
799     @Override
800     public List<RouteNodeInstance> getActiveRouteNodeInstances() {
801         return getWorkflowDocumentService().getActiveRouteNodeInstances(getDocumentId());
802     }
803 
804     @Override
805     public List<RouteNodeInstance> getCurrentRouteNodeInstances() {
806         return getWorkflowDocumentService().getCurrentRouteNodeInstances(getDocumentId());
807     }
808 
809     @Override
810     public List<RouteNodeInstance> getRouteNodeInstances() {
811         return getWorkflowDocumentService().getRouteNodeInstances(getDocumentId());
812     }
813 
814     @Override
815     public List<String> getPreviousNodeNames() {
816         return getWorkflowDocumentService().getPreviousRouteNodeNames(getDocumentId());
817     }
818 
819     @Override
820     public DocumentDetail getDocumentDetail() {
821         return getWorkflowDocumentService().getDocumentDetail(getDocumentId());
822     }
823 
824     @Override
825     public void updateDocumentContent(DocumentContentUpdate documentContentUpdate) {
826         if (documentContentUpdate == null) {
827             throw new IllegalArgumentException("documentContentUpdate was null.");
828         }
829         getModifiableDocumentContent().setDocumentContentUpdate(documentContentUpdate);
830     }
831 
832     @Override
833     public void placeInExceptionRouting(String annotation) {
834         DocumentActionResult result = getWorkflowDocumentActionsService().placeInExceptionRouting(
835                 constructDocumentActionParameters(annotation));
836         resetStateAfterAction(result);
837     }
838 
839     @Override
840     public void setVariable(String name, String value) {
841         getModifiableDocument().setVariable(name, value);
842     }
843 
844     @Override
845     public String getVariableValue(String name) {
846         return getModifiableDocument().getVariableValue(name);
847     }
848 
849     @Override
850     public void setReceiveFutureRequests() {
851         setVariable(getFutureRequestsKey(principalId), getReceiveFutureRequestsValue());
852     }
853 
854     @Override
855     public void setDoNotReceiveFutureRequests() {
856         this.setVariable(getFutureRequestsKey(principalId), getDoNotReceiveFutureRequestsValue());
857     }
858 
859     @Override
860     public void setClearFutureRequests() {
861         this.setVariable(getFutureRequestsKey(principalId), getClearFutureRequestsValue());
862     }
863 
864     protected String getFutureRequestsKey(String principalId) {
865         return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + ","
866                 + new Date().toString() + ", " + Math.random();
867     }
868 
869     @Override
870     public String getReceiveFutureRequestsValue() {
871         return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
872     }
873 
874     @Override
875     public String getDoNotReceiveFutureRequestsValue() {
876         return KewApiConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
877     }
878 
879     @Override
880     public String getClearFutureRequestsValue() {
881         return KewApiConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
882     }
883 
884     protected DocumentActionParameters constructDocumentActionParameters(String annotation) {
885         DocumentActionParameters.Builder builder = DocumentActionParameters.Builder.create(getDocumentId(),
886                 getPrincipalId());
887         builder.setAnnotation(annotation);
888         builder.setDocumentUpdate(getDocumentUpdateIfDirty());
889         builder.setDocumentContentUpdate(getDocumentContentUpdateIfDirty());
890         return builder.build();
891     }
892     
893     @Override
894     public DateTime getDateLastModified() {
895         return getDocument().getDateLastModified();
896     }
897 
898     @Override
899     public DateTime getDateApproved() {
900         return getDocument().getDateApproved();
901     }
902 
903     @Override
904     public DateTime getDateFinalized() {
905         return getDocument().getDateFinalized();
906     }
907 
908     @Override
909     public String getInitiatorPrincipalId() {
910         return getDocument().getInitiatorPrincipalId();
911     }
912 
913     @Override
914     public String getRoutedByPrincipalId() {
915         return getDocument().getRoutedByPrincipalId();
916     }
917 
918     @Override
919     public String getDocumentTypeId() {
920         return getDocument().getDocumentTypeId();
921     }
922 
923     @Override
924     public String getDocumentHandlerUrl() {
925         return getDocument().getDocumentHandlerUrl();
926     }
927 
928     @Override
929     public String getApplicationDocumentStatus() {
930         return getDocument().getApplicationDocumentStatus();
931     }
932 
933     @Override
934     public DateTime getApplicationDocumentStatusDate() {
935         return getDocument().getApplicationDocumentStatusDate();
936     }
937 
938     @Override
939     public Map<String, String> getVariables() {
940         return getDocument().getVariables();
941     }
942 
943     /**
944      * A wrapper around DocumentContent which keeps track of local changes and generates
945      * a new updated DocumentContent as necessary.
946      */
947     protected static class ModifiableDocumentContent implements Serializable {
948 
949         private static final long serialVersionUID = -4458431160327214042L;
950 
951         private boolean dirty;
952         private DocumentContent originalDocumentContent;
953         private DocumentContentUpdate.Builder builder;
954 
955         protected ModifiableDocumentContent(DocumentContent documentContent) {
956             this.dirty = false;
957             this.originalDocumentContent = documentContent;
958             this.builder = DocumentContentUpdate.Builder.create(documentContent);
959         }
960 
961         protected DocumentContent getDocumentContent() {
962             if (!dirty) {
963                 return originalDocumentContent;
964             }
965             DocumentContent.Builder documentContentBuilder = DocumentContent.Builder.create(originalDocumentContent);
966             documentContentBuilder.setApplicationContent(builder.getApplicationContent());
967             documentContentBuilder.setAttributeContent(builder.getAttributeContent());
968             documentContentBuilder.setSearchableContent(builder.getSearchableContent());
969             return documentContentBuilder.build();
970         }
971 
972         protected DocumentContentUpdate build() {
973             return builder.build();
974         }
975 
976         protected void setDocumentContentUpdate(DocumentContentUpdate update) {
977             this.builder = DocumentContentUpdate.Builder.create(update);
978             this.dirty = true;
979         }
980 
981         protected void addAttributeDefinition(WorkflowAttributeDefinition definition) {
982             builder.getAttributeDefinitions().add(definition);
983             dirty = true;
984         }
985 
986         protected void removeAttributeDefinition(WorkflowAttributeDefinition definition) {
987             builder.getAttributeDefinitions().remove(definition);
988             dirty = true;
989         }
990 
991         protected List<WorkflowAttributeDefinition> getAttributeDefinitions() {
992             return builder.getAttributeDefinitions();
993         }
994 
995         protected void addSearchableDefinition(WorkflowAttributeDefinition definition) {
996             builder.getSearchableDefinitions().add(definition);
997             dirty = true;
998         }
999 
1000         protected void removeSearchableDefinition(WorkflowAttributeDefinition definition) {
1001             builder.getSearchableDefinitions().remove(definition);
1002             dirty = true;
1003         }
1004 
1005         protected List<WorkflowAttributeDefinition> getSearchableDefinitions() {
1006             return builder.getAttributeDefinitions();
1007         }
1008 
1009         protected void setApplicationContent(String applicationContent) {
1010             builder.setApplicationContent(applicationContent);
1011             dirty = true;
1012         }
1013 
1014         protected void setAttributeContent(String attributeContent) {
1015             builder.setAttributeContent(attributeContent);
1016             dirty = true;
1017         }
1018 
1019         public void setAttributeDefinitions(List<WorkflowAttributeDefinition> attributeDefinitions) {
1020             builder.setAttributeDefinitions(attributeDefinitions);
1021             dirty = true;
1022         }
1023 
1024         public void setSearchableContent(String searchableContent) {
1025             builder.setSearchableContent(searchableContent);
1026             dirty = true;
1027         }
1028 
1029         public void setSearchableDefinitions(List<WorkflowAttributeDefinition> searchableDefinitions) {
1030             builder.setSearchableDefinitions(searchableDefinitions);
1031             dirty = true;
1032         }
1033 
1034         boolean isDirty() {
1035             return dirty;
1036         }
1037 
1038     }
1039 
1040     /**
1041      * A wrapper around Document which keeps track of local changes and generates
1042      * a new updated Document as necessary.
1043      */
1044     protected static class ModifiableDocument implements Serializable {
1045 
1046         private static final long serialVersionUID = -3234793238863410378L;
1047 
1048         private boolean dirty;
1049         private Document originalDocument;
1050         private DocumentUpdate.Builder builder;
1051 
1052         protected ModifiableDocument(Document document) {
1053             this.dirty = false;
1054             this.originalDocument = document;
1055             this.builder = DocumentUpdate.Builder.create(document);
1056         }
1057 
1058         protected Document getDocument() {
1059             if (!dirty) {
1060                 return originalDocument;
1061             }
1062             Document.Builder documentBuilder = Document.Builder.create(originalDocument);
1063             documentBuilder.setApplicationDocumentId(builder.getApplicationDocumentId());
1064             documentBuilder.setTitle(builder.getTitle());
1065             return documentBuilder.build();
1066         }
1067 
1068         protected DocumentUpdate build() {
1069             return builder.build();
1070         }
1071 
1072         /**
1073          * Immutable value which is accessed frequently, provide direct access to it.
1074          */
1075         protected String getDocumentId() {
1076             return originalDocument.getDocumentId();
1077         }
1078 
1079         /**
1080          * Immutable value which is accessed frequently, provide direct access to it.
1081          */
1082         protected DateTime getDateCreated() {
1083             return originalDocument.getDateCreated();
1084         }
1085 
1086         protected String getApplicationDocumentId() {
1087             return builder.getApplicationDocumentId();
1088         }
1089 
1090         protected void setApplicationDocumentId(String applicationDocumentId) {
1091             builder.setApplicationDocumentId(applicationDocumentId);
1092             dirty = true;
1093         }
1094 
1095         protected String getTitle() {
1096             return builder.getTitle();
1097         }
1098 
1099         protected void setTitle(String title) {
1100             builder.setTitle(title);
1101             dirty = true;
1102         }
1103 
1104         protected String getApplicationDocumentStatus() {
1105             return builder.getApplicationDocumentStatus();
1106         }
1107 
1108         protected void setApplicationDocumentStatus(String applicationDocumentStatus) {
1109             builder.setApplicationDocumentStatus(applicationDocumentStatus);
1110             dirty = true;
1111         }
1112 
1113         protected void setVariable(String name, String value) {
1114             builder.setVariable(name, value);
1115             dirty = true;
1116         }
1117 
1118         protected String getVariableValue(String name) {
1119             return builder.getVariableValue(name);
1120         }
1121 
1122         boolean isDirty() {
1123             return dirty;
1124         }
1125 
1126     }
1127 
1128 }