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