001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.kew.impl.document;
017    
018    import java.io.Serializable;
019    import java.util.Arrays;
020    import java.util.Collections;
021    import java.util.Date;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.joda.time.DateTime;
029    import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract;
030    import org.kuali.rice.kew.api.KewApiConstants;
031    import org.kuali.rice.kew.api.KewApiServiceLocator;
032    import org.kuali.rice.kew.api.action.ActionRequest;
033    import org.kuali.rice.kew.api.action.ActionRequestType;
034    import org.kuali.rice.kew.api.action.ActionTaken;
035    import org.kuali.rice.kew.api.action.ActionType;
036    import org.kuali.rice.kew.api.action.AdHocRevoke;
037    import org.kuali.rice.kew.api.action.AdHocToGroup;
038    import org.kuali.rice.kew.api.action.AdHocToPrincipal;
039    import org.kuali.rice.kew.api.action.DocumentActionParameters;
040    import org.kuali.rice.kew.api.action.DocumentActionResult;
041    import org.kuali.rice.kew.api.action.MovePoint;
042    import org.kuali.rice.kew.api.action.RequestedActions;
043    import org.kuali.rice.kew.api.action.ReturnPoint;
044    import org.kuali.rice.kew.api.action.ValidActions;
045    import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
046    import org.kuali.rice.kew.api.document.Document;
047    import org.kuali.rice.kew.api.document.DocumentContent;
048    import org.kuali.rice.kew.api.document.DocumentContentUpdate;
049    import org.kuali.rice.kew.api.document.DocumentDetail;
050    import org.kuali.rice.kew.api.document.DocumentStatus;
051    import org.kuali.rice.kew.api.document.DocumentUpdate;
052    import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
053    import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
054    import 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     */
063    public 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    }