View Javadoc
1   /**
2    * Copyright 2005-2015 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 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 }