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        return getValidActions().getValidActions().contains(actionType);
566    }
567
568    @Override
569    public void superUserBlanketApprove(String annotation) {
570        DocumentActionResult result = getWorkflowDocumentActionsService().superUserBlanketApprove(
571                constructDocumentActionParameters(annotation), true);
572        resetStateAfterAction(result);
573    }
574
575    @Override
576    public void superUserNodeApprove(String nodeName, String annotation) {
577        DocumentActionResult result = getWorkflowDocumentActionsService().superUserNodeApprove(
578                constructDocumentActionParameters(annotation), true, nodeName);
579        resetStateAfterAction(result);
580    }
581
582    @Override
583    public void superUserTakeRequestedAction(String actionRequestId, String annotation) {
584        DocumentActionResult result = getWorkflowDocumentActionsService().superUserTakeRequestedAction(
585                constructDocumentActionParameters(annotation), true, actionRequestId);
586        resetStateAfterAction(result);
587    }
588
589    @Override
590    public void superUserDisapprove(String annotation) {
591        DocumentActionResult result = getWorkflowDocumentActionsService().superUserDisapprove(
592                constructDocumentActionParameters(annotation), true);
593        resetStateAfterAction(result);
594    }
595
596    @Override
597    public void superUserCancel(String annotation) {
598        DocumentActionResult result = getWorkflowDocumentActionsService().superUserCancel(
599                constructDocumentActionParameters(annotation), true);
600        resetStateAfterAction(result);
601    }
602
603    @Override
604    public void superUserReturnToPreviousNode(ReturnPoint returnPoint, String annotation) {
605        DocumentActionResult result = getWorkflowDocumentActionsService().superUserReturnToPreviousNode(
606                constructDocumentActionParameters(annotation), true, returnPoint);
607        resetStateAfterAction(result);
608    }
609
610    @Override
611    public void complete(String annotation) {
612        DocumentActionResult result = getWorkflowDocumentActionsService().complete(
613                constructDocumentActionParameters(annotation));
614        resetStateAfterAction(result);
615    }
616
617    @Override
618    public void logAnnotation(String annotation) {
619        getWorkflowDocumentActionsService().logAnnotation(getDocumentId(), principalId, annotation);
620    }
621
622    @Override
623    public DocumentStatus getStatus() {
624        return getDocument().getStatus();
625    }
626
627    @Override
628    public boolean checkStatus(DocumentStatus status) {
629        if (status == null) {
630            throw new IllegalArgumentException("status was null");
631        }
632        return status == getStatus();
633    }
634
635    /**
636     * Indicates if the document is in the initiated state or not.
637     * 
638     * @return true if in the specified state
639     */
640    @Override
641    public boolean isInitiated() {
642        return checkStatus(DocumentStatus.INITIATED);
643    }
644
645    /**
646     * Indicates if the document is in the saved state or not.
647     * 
648     * @return true if in the specified state
649     */
650    @Override
651    public boolean isSaved() {
652        return checkStatus(DocumentStatus.SAVED);
653    }
654
655    /**
656     * Indicates if the document is in the enroute state or not.
657     * 
658     * @return true if in the specified state
659     */
660    @Override
661    public boolean isEnroute() {
662        return checkStatus(DocumentStatus.ENROUTE);
663    }
664
665    /**
666     * Indicates if the document is in the exception state or not.
667     * 
668     * @return true if in the specified state
669     */
670    @Override
671    public boolean isException() {
672        return checkStatus(DocumentStatus.EXCEPTION);
673    }
674
675    /**
676     * Indicates if the document is in the canceled state or not.
677     * 
678     * @return true if in the specified state
679     */
680    @Override
681    public boolean isCanceled() {
682        return checkStatus(DocumentStatus.CANCELED);
683    }
684
685    /**
686     * Indicates if the document is in the recalled state or not.
687     *
688     * @return true if in the specified state
689     */
690    @Override
691    public boolean isRecalled() {
692        return checkStatus(DocumentStatus.RECALLED);
693    }
694
695    /**
696     * Indicates if the document is in the disapproved state or not.
697     * 
698     * @return true if in the specified state
699     */
700    @Override
701    public boolean isDisapproved() {
702        return checkStatus(DocumentStatus.DISAPPROVED);
703    }
704
705    /**
706     * Indicates if the document is in the Processed or Finalized state.
707     * 
708     * @return true if in the specified state
709     */
710    @Override
711    public boolean isApproved() {
712        return isProcessed() || isFinal();
713    }
714
715    /**
716     * Indicates if the document is in the processed state or not.
717     * 
718     * @return true if in the specified state
719     */
720    @Override
721    public boolean isProcessed() {
722        return checkStatus(DocumentStatus.PROCESSED);
723    }
724
725    /**
726     * Indicates if the document is in the final state or not.
727     * 
728     * @return true if in the specified state
729     */
730    @Override
731    public boolean isFinal() {
732        return checkStatus(DocumentStatus.FINAL);
733    }
734
735    /**
736     * Returns the principalId with which this WorkflowDocument was constructed
737     * 
738     * @return the principalId with which this WorkflowDocument was constructed
739     */
740    @Override
741    public String getPrincipalId() {
742        return principalId;
743    }
744
745    @Override
746    public void switchPrincipal(String principalId) {
747        if (StringUtils.isBlank(this.principalId)) {
748            throw new IllegalArgumentException("principalId was null or blank");
749        }
750        this.principalId = principalId;
751        this.validActions = null;
752        this.requestedActions = null;
753    }
754
755    @Override
756    public void takeGroupAuthority(String annotation, String groupId) {
757        DocumentActionResult result = getWorkflowDocumentActionsService().takeGroupAuthority(
758                constructDocumentActionParameters(annotation), groupId);
759        resetStateAfterAction(result);
760    }
761
762    @Override
763    public void releaseGroupAuthority(String annotation, String groupId) {
764        DocumentActionResult result = getWorkflowDocumentActionsService().releaseGroupAuthority(
765                constructDocumentActionParameters(annotation), groupId);
766        resetStateAfterAction(result);
767    }
768
769    @Override
770    public Set<String> getNodeNames() {
771        final List<String> names = getWorkflowDocumentService().getActiveRouteNodeNames(getDocumentId());
772        return Collections.unmodifiableSet(new HashSet<String>(names));
773    }
774
775    public Set<String> getCurrentNodeNames() {
776        final List<String> names = getWorkflowDocumentService().getCurrentRouteNodeNames(getDocumentId());
777        return Collections.unmodifiableSet(new HashSet<String>(names));
778    }
779
780    @Override
781    public void returnToPreviousNode(String annotation, String nodeName) {
782        if (nodeName == null) {
783            throw new IllegalArgumentException("nodeName was null");
784        }
785        returnToPreviousNode(annotation, ReturnPoint.create(nodeName));
786    }
787
788    @Override
789    public void returnToPreviousNode(String annotation, ReturnPoint returnPoint) {
790        if (returnPoint == null) {
791            throw new IllegalArgumentException("returnPoint was null");
792        }
793        DocumentActionResult result = getWorkflowDocumentActionsService().returnToPreviousNode(
794                constructDocumentActionParameters(annotation), returnPoint);
795        resetStateAfterAction(result);
796    }
797
798    @Override
799    public void move(MovePoint movePoint, String annotation) {
800        if (movePoint == null) {
801            throw new IllegalArgumentException("movePoint was null");
802        }
803        DocumentActionResult result = getWorkflowDocumentActionsService().move(
804                constructDocumentActionParameters(annotation), movePoint);
805        resetStateAfterAction(result);
806    }
807
808    @Override
809    public List<RouteNodeInstance> getActiveRouteNodeInstances() {
810        return getWorkflowDocumentService().getActiveRouteNodeInstances(getDocumentId());
811    }
812
813    @Override
814    public List<RouteNodeInstance> getCurrentRouteNodeInstances() {
815        return getWorkflowDocumentService().getCurrentRouteNodeInstances(getDocumentId());
816    }
817
818    @Override
819    public List<RouteNodeInstance> getRouteNodeInstances() {
820        return getWorkflowDocumentService().getRouteNodeInstances(getDocumentId());
821    }
822
823    @Override
824    public List<String> getPreviousNodeNames() {
825        return getWorkflowDocumentService().getPreviousRouteNodeNames(getDocumentId());
826    }
827
828    @Override
829    public DocumentDetail getDocumentDetail() {
830        return getWorkflowDocumentService().getDocumentDetail(getDocumentId());
831    }
832
833    @Override
834    public void updateDocumentContent(DocumentContentUpdate documentContentUpdate) {
835        if (documentContentUpdate == null) {
836            throw new IllegalArgumentException("documentContentUpdate was null.");
837        }
838        getModifiableDocumentContent().setDocumentContentUpdate(documentContentUpdate);
839    }
840
841    @Override
842    public void placeInExceptionRouting(String annotation) {
843        DocumentActionResult result = getWorkflowDocumentActionsService().placeInExceptionRouting(
844                constructDocumentActionParameters(annotation));
845        resetStateAfterAction(result);
846    }
847
848    @Override
849    public void setVariable(String name, String value) {
850        getModifiableDocument().setVariable(name, value);
851    }
852
853    @Override
854    public String getVariableValue(String name) {
855        return getModifiableDocument().getVariableValue(name);
856    }
857
858    @Override
859    public void setReceiveFutureRequests() {
860        setVariable(getFutureRequestsKey(principalId), getReceiveFutureRequestsValue());
861    }
862
863    @Override
864    public void setDoNotReceiveFutureRequests() {
865        this.setVariable(getFutureRequestsKey(principalId), getDoNotReceiveFutureRequestsValue());
866    }
867
868    @Override
869    public void setClearFutureRequests() {
870        this.setVariable(getFutureRequestsKey(principalId), getClearFutureRequestsValue());
871    }
872
873    protected String getFutureRequestsKey(String principalId) {
874        return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + ","
875                + new Date().toString() + ", " + Math.random();
876    }
877
878    @Override
879    public String getReceiveFutureRequestsValue() {
880        return KewApiConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
881    }
882
883    @Override
884    public String getDoNotReceiveFutureRequestsValue() {
885        return KewApiConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
886    }
887
888    @Override
889    public String getClearFutureRequestsValue() {
890        return KewApiConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
891    }
892
893    protected DocumentActionParameters constructDocumentActionParameters(String annotation) {
894        DocumentActionParameters.Builder builder = DocumentActionParameters.Builder.create(getDocumentId(),
895                getPrincipalId());
896        builder.setAnnotation(annotation);
897        builder.setDocumentUpdate(getDocumentUpdateIfDirty());
898        builder.setDocumentContentUpdate(getDocumentContentUpdateIfDirty());
899        return builder.build();
900    }
901    
902    @Override
903    public DateTime getDateLastModified() {
904        return getDocument().getDateLastModified();
905    }
906
907    @Override
908    public DateTime getDateApproved() {
909        return getDocument().getDateApproved();
910    }
911
912    @Override
913    public DateTime getDateFinalized() {
914        return getDocument().getDateFinalized();
915    }
916
917    @Override
918    public String getInitiatorPrincipalId() {
919        return getDocument().getInitiatorPrincipalId();
920    }
921
922    @Override
923    public String getRoutedByPrincipalId() {
924        return getDocument().getRoutedByPrincipalId();
925    }
926
927    @Override
928    public String getDocumentTypeId() {
929        return getDocument().getDocumentTypeId();
930    }
931
932    @Override
933    public String getDocumentHandlerUrl() {
934        return getDocument().getDocumentHandlerUrl();
935    }
936
937    @Override
938    public String getApplicationDocumentStatus() {
939        return getDocument().getApplicationDocumentStatus();
940    }
941
942    @Override
943    public DateTime getApplicationDocumentStatusDate() {
944        return getDocument().getApplicationDocumentStatusDate();
945    }
946
947    @Override
948    public Map<String, String> getVariables() {
949        return getDocument().getVariables();
950    }
951
952    /**
953     * A wrapper around DocumentContent which keeps track of local changes and generates
954     * a new updated DocumentContent as necessary.
955     */
956    protected static class ModifiableDocumentContent implements Serializable {
957
958        private static final long serialVersionUID = -4458431160327214042L;
959
960        private boolean dirty;
961        private DocumentContent originalDocumentContent;
962        private DocumentContentUpdate.Builder builder;
963
964        protected ModifiableDocumentContent(DocumentContent documentContent) {
965            this.dirty = false;
966            this.originalDocumentContent = documentContent;
967            this.builder = DocumentContentUpdate.Builder.create(documentContent);
968        }
969
970        protected DocumentContent getDocumentContent() {
971            if (!dirty) {
972                return originalDocumentContent;
973            }
974            DocumentContent.Builder documentContentBuilder = DocumentContent.Builder.create(originalDocumentContent);
975            documentContentBuilder.setApplicationContent(builder.getApplicationContent());
976            documentContentBuilder.setAttributeContent(builder.getAttributeContent());
977            documentContentBuilder.setSearchableContent(builder.getSearchableContent());
978            return documentContentBuilder.build();
979        }
980
981        protected DocumentContentUpdate build() {
982            return builder.build();
983        }
984
985        protected void setDocumentContentUpdate(DocumentContentUpdate update) {
986            this.builder = DocumentContentUpdate.Builder.create(update);
987            this.dirty = true;
988        }
989
990        protected void addAttributeDefinition(WorkflowAttributeDefinition definition) {
991            builder.getAttributeDefinitions().add(definition);
992            dirty = true;
993        }
994
995        protected void removeAttributeDefinition(WorkflowAttributeDefinition definition) {
996            builder.getAttributeDefinitions().remove(definition);
997            dirty = true;
998        }
999
1000        protected List<WorkflowAttributeDefinition> getAttributeDefinitions() {
1001            return builder.getAttributeDefinitions();
1002        }
1003
1004        protected void addSearchableDefinition(WorkflowAttributeDefinition definition) {
1005            builder.getSearchableDefinitions().add(definition);
1006            dirty = true;
1007        }
1008
1009        protected void removeSearchableDefinition(WorkflowAttributeDefinition definition) {
1010            builder.getSearchableDefinitions().remove(definition);
1011            dirty = true;
1012        }
1013
1014        protected List<WorkflowAttributeDefinition> getSearchableDefinitions() {
1015            return builder.getAttributeDefinitions();
1016        }
1017
1018        protected void setApplicationContent(String applicationContent) {
1019            builder.setApplicationContent(applicationContent);
1020            dirty = true;
1021        }
1022
1023        protected void setAttributeContent(String attributeContent) {
1024            builder.setAttributeContent(attributeContent);
1025            dirty = true;
1026        }
1027
1028        public void setAttributeDefinitions(List<WorkflowAttributeDefinition> attributeDefinitions) {
1029            builder.setAttributeDefinitions(attributeDefinitions);
1030            dirty = true;
1031        }
1032
1033        public void setSearchableContent(String searchableContent) {
1034            builder.setSearchableContent(searchableContent);
1035            dirty = true;
1036        }
1037
1038        public void setSearchableDefinitions(List<WorkflowAttributeDefinition> searchableDefinitions) {
1039            builder.setSearchableDefinitions(searchableDefinitions);
1040            dirty = true;
1041        }
1042
1043        boolean isDirty() {
1044            return dirty;
1045        }
1046
1047    }
1048
1049    /**
1050     * A wrapper around Document which keeps track of local changes and generates
1051     * a new updated Document as necessary.
1052     */
1053    protected static class ModifiableDocument implements Serializable {
1054
1055        private static final long serialVersionUID = -3234793238863410378L;
1056
1057        private boolean dirty;
1058        private Document originalDocument;
1059        private DocumentUpdate.Builder builder;
1060
1061        protected ModifiableDocument(Document document) {
1062            this.dirty = false;
1063            this.originalDocument = document;
1064            this.builder = DocumentUpdate.Builder.create(document);
1065        }
1066
1067        protected Document getDocument() {
1068            if (!dirty) {
1069                return originalDocument;
1070            }
1071            Document.Builder documentBuilder = Document.Builder.create(originalDocument);
1072            documentBuilder.setApplicationDocumentId(builder.getApplicationDocumentId());
1073            documentBuilder.setTitle(builder.getTitle());
1074            documentBuilder.setApplicationDocumentStatus(builder.getApplicationDocumentStatus());
1075            documentBuilder.setVariables(builder.getVariables());
1076            return documentBuilder.build();
1077        }
1078
1079        protected DocumentUpdate build() {
1080            return builder.build();
1081        }
1082
1083        /**
1084         * Immutable value which is accessed frequently, provide direct access to it.
1085         */
1086        protected String getDocumentId() {
1087            return originalDocument.getDocumentId();
1088        }
1089
1090        /**
1091         * Immutable value which is accessed frequently, provide direct access to it.
1092         */
1093        protected DateTime getDateCreated() {
1094            return originalDocument.getDateCreated();
1095        }
1096
1097        protected String getApplicationDocumentId() {
1098            return builder.getApplicationDocumentId();
1099        }
1100
1101        protected void setApplicationDocumentId(String applicationDocumentId) {
1102            builder.setApplicationDocumentId(applicationDocumentId);
1103            dirty = true;
1104        }
1105
1106        protected String getTitle() {
1107            return builder.getTitle();
1108        }
1109
1110        protected void setTitle(String title) {
1111            builder.setTitle(title);
1112            dirty = true;
1113        }
1114
1115        protected String getApplicationDocumentStatus() {
1116            return builder.getApplicationDocumentStatus();
1117        }
1118
1119        protected void setApplicationDocumentStatus(String applicationDocumentStatus) {
1120            builder.setApplicationDocumentStatus(applicationDocumentStatus);
1121            dirty = true;
1122        }
1123
1124        protected void setVariable(String name, String value) {
1125            builder.setVariable(name, value);
1126            dirty = true;
1127        }
1128
1129        protected String getVariableValue(String name) {
1130            return builder.getVariableValue(name);
1131        }
1132
1133        boolean isDirty() {
1134            return dirty;
1135        }
1136
1137    }
1138
1139}