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