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.routeheader;
017    
018    import org.apache.commons.lang.ObjectUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.log4j.Logger;
021    import org.hibernate.annotations.Fetch;
022    import org.hibernate.annotations.FetchMode;
023    import org.hibernate.annotations.GenericGenerator;
024    import org.hibernate.annotations.Parameter;
025    import org.joda.time.DateTime;
026    import org.kuali.rice.core.api.exception.RiceRuntimeException;
027    import org.kuali.rice.kew.actionitem.ActionItem;
028    import org.kuali.rice.kew.actionlist.CustomActionListAttribute;
029    import org.kuali.rice.kew.actionlist.DefaultCustomActionListAttribute;
030    import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
031    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
032    import org.kuali.rice.kew.actiontaken.ActionTakenValue;
033    import org.kuali.rice.kew.api.KewApiConstants;
034    import org.kuali.rice.kew.api.WorkflowRuntimeException;
035    import org.kuali.rice.kew.api.action.ActionType;
036    import org.kuali.rice.kew.api.document.Document;
037    import org.kuali.rice.kew.api.document.DocumentContract;
038    import org.kuali.rice.kew.api.document.DocumentStatus;
039    import org.kuali.rice.kew.api.document.DocumentUpdate;
040    import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
041    import org.kuali.rice.kew.api.exception.WorkflowException;
042    import org.kuali.rice.kew.api.util.CodeTranslator;
043    import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaEbo;
044    import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
045    import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
046    import org.kuali.rice.kew.doctype.DocumentTypePolicy;
047    import org.kuali.rice.kew.doctype.bo.DocumentType;
048    import org.kuali.rice.kew.engine.CompatUtils;
049    import org.kuali.rice.kew.engine.node.Branch;
050    import org.kuali.rice.kew.engine.node.BranchState;
051    import org.kuali.rice.kew.engine.node.RouteNode;
052    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
053    import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
054    import org.kuali.rice.kew.mail.CustomEmailAttribute;
055    import org.kuali.rice.kew.mail.CustomEmailAttributeImpl;
056    import org.kuali.rice.kew.notes.CustomNoteAttribute;
057    import org.kuali.rice.kew.notes.CustomNoteAttributeImpl;
058    import org.kuali.rice.kew.notes.Note;
059    import org.kuali.rice.kew.service.KEWServiceLocator;
060    import org.kuali.rice.kim.api.identity.principal.Principal;
061    import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
062    
063    import javax.persistence.CascadeType;
064    import javax.persistence.Column;
065    import javax.persistence.Entity;
066    import javax.persistence.FetchType;
067    import javax.persistence.GeneratedValue;
068    import javax.persistence.Id;
069    import javax.persistence.JoinColumn;
070    import javax.persistence.JoinTable;
071    import javax.persistence.ManyToMany;
072    import javax.persistence.NamedQueries;
073    import javax.persistence.NamedQuery;
074    import javax.persistence.OneToMany;
075    import javax.persistence.OrderBy;
076    import javax.persistence.Table;
077    import javax.persistence.Transient;
078    import java.sql.Timestamp;
079    import java.util.ArrayList;
080    import java.util.Collection;
081    import java.util.HashMap;
082    import java.util.Iterator;
083    import java.util.List;
084    import java.util.Map;
085    
086    
087    
088    /**
089     * A document within KEW.  A document effectively represents a process that moves through
090     * the workflow engine.  It is created from a particular {@link DocumentType} and follows
091     * the route path defined by that DocumentType.
092     *
093     * <p>During a document's lifecycle it progresses through a series of statuses, starting
094     * with INITIATED and moving to one of the terminal states (such as FINAL, CANCELED, etc).
095     * The list of status on a document are defined in the {@link KewApiConstants} class and
096     * include the constants starting with "ROUTE_HEADER_" and ending with "_CD".
097     *
098     * <p>Associated with the document is the document content.  The document content is XML
099     * which represents the content of that document.  This XML content is typically used
100     * to make routing decisions for the document.
101     *
102     * <p>A document has associated with it a set of {@link ActionRequestValue} object and
103     * {@link ActionTakenValue} objects.  Action Requests represent requests for user
104     * action (such as Approve, Acknowledge, etc).  Action Takens represent action that
105     * users have performed on the document, such as approvals or cancelling of the document.
106     *
107     * <p>The instantiated route path of a document is defined by it's graph of
108     * {@link RouteNodeInstance} objects.  The path starts at the initial node of the document
109     * and progresses from there following the next nodes of each node instance.  The current
110     * active nodes on the document are defined by the "active" flag on the node instance
111     * where are not marked as "complete".
112     *
113     * @see DocumentType
114     * @see ActionRequestValue
115     * @see ActionItem
116     * @see ActionTakenValue
117     * @see RouteNodeInstance
118     * @see KewApiConstants
119     *
120     * @author Kuali Rice Team (rice.collab@kuali.org)
121     */
122    @Entity
123    @Table(name="KREW_DOC_HDR_T")
124    //@Sequence(name="KREW_DOC_HDR_S", property="documentId")
125    @NamedQueries({
126        @NamedQuery(name="DocumentRouteHeaderValue.FindByDocumentId", query="select d from DocumentRouteHeaderValue as d where d.documentId = :documentId"),
127        @NamedQuery(name="DocumentRouteHeaderValue.QuickLinks.FindWatchedDocumentsByInitiatorWorkflowId", query="SELECT NEW org.kuali.rice.kew.quicklinks.WatchedDocument(documentId, docRouteStatus, docTitle) FROM DocumentRouteHeaderValue WHERE initiatorWorkflowId = :initiatorWorkflowId AND docRouteStatus IN ('"+ KewApiConstants.ROUTE_HEADER_ENROUTE_CD +"','"+ KewApiConstants.ROUTE_HEADER_EXCEPTION_CD +"') ORDER BY createDate DESC"),
128        @NamedQuery(name="DocumentRouteHeaderValue.GetAppDocId", query="SELECT d.appDocId from DocumentRouteHeaderValue as d where d.documentId = :documentId"),
129        @NamedQuery(name="DocumentRouteHeaderValue.GetAppDocStatus", query="SELECT d.appDocStatus from DocumentRouteHeaderValue as d where d.documentId = :documentId")
130    })
131    public class DocumentRouteHeaderValue extends PersistableBusinessObjectBase implements DocumentContract, DocumentSearchCriteriaEbo {
132        private static final long serialVersionUID = -4700736340527913220L;
133        private static final Logger LOG = Logger.getLogger(DocumentRouteHeaderValue.class);
134    
135        public static final String CURRENT_ROUTE_NODE_NAME_DELIMITER = ", ";
136    
137        @Column(name="DOC_TYP_ID")
138        private String documentTypeId;
139        @Column(name="DOC_HDR_STAT_CD")
140        private java.lang.String docRouteStatus;
141        @Column(name="RTE_LVL")
142        private java.lang.Integer docRouteLevel;
143        @Column(name="STAT_MDFN_DT")
144        private java.sql.Timestamp dateModified;
145        @Column(name="CRTE_DT")
146        private java.sql.Timestamp createDate;
147        @Column(name="APRV_DT")
148        private java.sql.Timestamp approvedDate;
149        @Column(name="FNL_DT")
150        private java.sql.Timestamp finalizedDate;
151        @Transient
152        private DocumentRouteHeaderValueContent documentContent;
153        @Column(name="TTL")
154        private java.lang.String docTitle;
155        @Column(name="APP_DOC_ID")
156        private java.lang.String appDocId;
157        @Column(name="DOC_VER_NBR")
158        private java.lang.Integer docVersion = new Integer(KewApiConstants.DocumentContentVersions.NODAL);
159        @Column(name="INITR_PRNCPL_ID")
160        private java.lang.String initiatorWorkflowId;
161        @Column(name="RTE_PRNCPL_ID")
162        private java.lang.String routedByUserWorkflowId;
163        @Column(name="RTE_STAT_MDFN_DT")
164        private java.sql.Timestamp routeStatusDate;
165        @Column(name="APP_DOC_STAT")
166        private java.lang.String appDocStatus;
167        @Column(name="APP_DOC_STAT_MDFN_DT")
168        private java.sql.Timestamp appDocStatusDate;
169    
170        @Id
171        @GeneratedValue(generator="KREW_DOC_HDR_S")
172        @GenericGenerator(name="KREW_DOC_HDR_S",strategy="org.hibernate.id.enhanced.SequenceStyleGenerator",parameters={
173                @Parameter(name="sequence_name",value="KREW_DOC_HDR_S"),
174                @Parameter(name="value_column",value="id")
175        })
176        @Column(name="DOC_HDR_ID")
177        private java.lang.String documentId;
178    
179        //@OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.REMOVE, mappedBy="routeHeader")
180        //@Fetch(value = FetchMode.SELECT)
181        //private List<ActionRequestValue> actionRequests = new ArrayList<ActionRequestValue>();
182    
183        //@OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.REMOVE, mappedBy="routeHeader")
184        //@OrderBy("actionDate ASC")
185        //@Fetch(value = FetchMode.SELECT)
186        //private List<ActionTakenValue> actionsTaken = new ArrayList<ActionTakenValue>();
187    
188        //@OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.REMOVE, mappedBy="routeHeader")
189        //@Fetch(value = FetchMode.SELECT)
190        //private List<ActionItem> actionItems = new ArrayList<ActionItem>();
191    
192        /**
193         * The appDocStatusHistory keeps a list of Application Document Status transitions
194         * for the document.  It tracks the previous status, the new status, and a timestamp of the 
195         * transition for each status transition.
196         */
197        @OneToMany(fetch=FetchType.EAGER, cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, mappedBy="documentId")
198        //@JoinColumn(referencedColumnName="DOC_HDR_ID")
199        @OrderBy("statusTransitionId ASC")
200        @Fetch(value = FetchMode.SELECT)
201        private List<DocumentStatusTransition> appDocStatusHistory = new ArrayList<DocumentStatusTransition>();
202    
203        @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REMOVE})
204        @JoinColumn(name="DOC_HDR_ID")
205        @OrderBy("noteId ASC")
206        private List<Note> notes = new ArrayList<Note>();
207    
208        @Transient
209        private List<SearchableAttributeValue> searchableAttributeValues = new ArrayList<SearchableAttributeValue>();
210        @Transient
211        private Collection queueItems = new ArrayList();
212        @Transient
213        private boolean routingReport = false;
214        @Transient
215        private List<ActionRequestValue> simulatedActionRequests;
216    
217        private static final boolean FINAL_STATE = true;
218        protected static final HashMap<String,String> legalActions;
219        protected static final HashMap<String,String> stateTransitionMap;
220    
221        /* New Workflow 2.1 Field */
222        @ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.REMOVE)
223        @JoinTable(name = "KREW_INIT_RTE_NODE_INSTN_T", joinColumns = @JoinColumn(name = "DOC_HDR_ID"), inverseJoinColumns = @JoinColumn(name = "RTE_NODE_INSTN_ID")) 
224        @Fetch(value = FetchMode.SELECT)
225        private List<RouteNodeInstance> initialRouteNodeInstances = new ArrayList<RouteNodeInstance>();
226    
227        // an empty list of target document statuses or legal actions
228        private static final String TERMINAL = "";
229    
230        static {
231            stateTransitionMap = new HashMap<String,String>();
232            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_INITIATED_CD, KewApiConstants.ROUTE_HEADER_SAVED_CD + KewApiConstants.ROUTE_HEADER_ENROUTE_CD + KewApiConstants.ROUTE_HEADER_CANCEL_CD);
233    
234            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_SAVED_CD, KewApiConstants.ROUTE_HEADER_SAVED_CD + KewApiConstants.ROUTE_HEADER_ENROUTE_CD + KewApiConstants.ROUTE_HEADER_CANCEL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD);
235    
236            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_ENROUTE_CD, KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD +
237                    KewApiConstants.ROUTE_HEADER_CANCEL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD + KewApiConstants.ROUTE_HEADER_EXCEPTION_CD + KewApiConstants.ROUTE_HEADER_SAVED_CD
238                    + DocumentStatus.RECALLED.getCode());
239            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, TERMINAL);
240            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_CANCEL_CD, TERMINAL);
241            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_FINAL_CD, TERMINAL);
242            stateTransitionMap.put(DocumentStatus.RECALLED.getCode(), TERMINAL);
243            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_EXCEPTION_CD, KewApiConstants.ROUTE_HEADER_EXCEPTION_CD + KewApiConstants.ROUTE_HEADER_ENROUTE_CD + KewApiConstants.ROUTE_HEADER_CANCEL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD + KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD + KewApiConstants.ROUTE_HEADER_SAVED_CD);
244            stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, KewApiConstants.ROUTE_HEADER_FINAL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD);
245    
246            legalActions = new HashMap<String,String>();
247            legalActions.put(KewApiConstants.ROUTE_HEADER_INITIATED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_SAVED_CD + KewApiConstants.ACTION_TAKEN_COMPLETED_CD + KewApiConstants.ACTION_TAKEN_ROUTED_CD + KewApiConstants.ACTION_TAKEN_CANCELED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + KewApiConstants.ACTION_TAKEN_MOVE_CD);
248            legalActions.put(KewApiConstants.ROUTE_HEADER_SAVED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_SAVED_CD + KewApiConstants.ACTION_TAKEN_COMPLETED_CD + KewApiConstants.ACTION_TAKEN_ROUTED_CD + KewApiConstants.ACTION_TAKEN_APPROVED_CD + KewApiConstants.ACTION_TAKEN_CANCELED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + KewApiConstants.ACTION_TAKEN_MOVE_CD);
249            /* ACTION_TAKEN_ROUTED_CD not included in enroute state
250             * ACTION_TAKEN_SAVED_CD removed as of version 2.4
251             */
252            legalActions.put(KewApiConstants.ROUTE_HEADER_ENROUTE_CD, /*KewApiConstants.ACTION_TAKEN_SAVED_CD + KewApiConstants.ACTION_TAKEN_ROUTED_CD + */KewApiConstants.ACTION_TAKEN_APPROVED_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ADHOC_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + KewApiConstants.ACTION_TAKEN_CANCELED_CD + KewApiConstants.ACTION_TAKEN_COMPLETED_CD + KewApiConstants.ACTION_TAKEN_DENIED_CD + KewApiConstants.ACTION_TAKEN_SU_APPROVED_CD + KewApiConstants.ACTION_TAKEN_SU_CANCELED_CD + KewApiConstants.ACTION_TAKEN_SU_DISAPPROVED_CD + KewApiConstants.ACTION_TAKEN_SU_ROUTE_LEVEL_APPROVED_CD + KewApiConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD + KewApiConstants.ACTION_TAKEN_SU_RETURNED_TO_PREVIOUS_CD + KewApiConstants.ACTION_TAKEN_MOVE_CD + ActionType.RECALL.getCode());
253            /* ACTION_TAKEN_ROUTED_CD not included in exception state
254             * ACTION_TAKEN_SAVED_CD removed as of version 2.4.2
255             */
256            legalActions.put(KewApiConstants.ROUTE_HEADER_EXCEPTION_CD, /*KewApiConstants.ACTION_TAKEN_SAVED_CD + */KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD + KewApiConstants.ACTION_TAKEN_APPROVED_CD + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + KewApiConstants.ACTION_TAKEN_CANCELED_CD + KewApiConstants.ACTION_TAKEN_COMPLETED_CD + KewApiConstants.ACTION_TAKEN_DENIED_CD + KewApiConstants.ACTION_TAKEN_SU_APPROVED_CD + KewApiConstants.ACTION_TAKEN_SU_CANCELED_CD + KewApiConstants.ACTION_TAKEN_SU_DISAPPROVED_CD + KewApiConstants.ACTION_TAKEN_SU_ROUTE_LEVEL_APPROVED_CD + KewApiConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD + KewApiConstants.ACTION_TAKEN_SU_RETURNED_TO_PREVIOUS_CD + KewApiConstants.ACTION_TAKEN_MOVE_CD);
257            legalActions.put(KewApiConstants.ROUTE_HEADER_FINAL_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD);
258            legalActions.put(KewApiConstants.ROUTE_HEADER_CANCEL_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD);
259            legalActions.put(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD);
260            legalActions.put(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD);
261            legalActions.put(DocumentStatus.RECALLED.getCode(), TERMINAL);
262        }
263    
264        public DocumentRouteHeaderValue() {
265        }
266    
267        public Principal getInitiatorPrincipal() {
268            // if we are running a simulation, there will be no initiator
269            if (getInitiatorWorkflowId() == null) {
270                return null;
271            }
272            return KEWServiceLocator.getIdentityHelperService().getPrincipal(getInitiatorWorkflowId());
273        }
274    
275        public Principal getRoutedByPrincipal()
276        {
277            if (getRoutedByUserWorkflowId() == null) {
278                return null;
279            }
280            return KEWServiceLocator.getIdentityHelperService().getPrincipal(getRoutedByUserWorkflowId());
281        }
282    
283        public String getInitiatorDisplayName() {
284            return KEWServiceLocator.getIdentityHelperService().getPerson(getInitiatorWorkflowId()).getName();
285        }
286    
287        public String getRoutedByDisplayName() {
288            return KEWServiceLocator.getIdentityHelperService().getPerson(getRoutedByUserWorkflowId()).getName();
289        }
290    
291        public String getCurrentRouteLevelName() {
292            String name = "Not Found";
293            // TODO the isRouteLevelDocument junk can be ripped out
294            if(routingReport){
295                name = "Routing Report";
296            } else if (CompatUtils.isRouteLevelDocument(this)) {
297                int routeLevelInt = getDocRouteLevel().intValue();
298                LOG.info("Getting current route level name for a Route level document: " + routeLevelInt+CURRENT_ROUTE_NODE_NAME_DELIMITER+documentId);
299                List routeLevelNodes = CompatUtils.getRouteLevelCompatibleNodeList(getDocumentType());
300                LOG.info("Route level compatible node list has " + routeLevelNodes.size() + " nodes");
301                if (routeLevelInt < routeLevelNodes.size()) {
302                    name = ((RouteNode)routeLevelNodes.get(routeLevelInt)).getRouteNodeName();
303                }
304            } else {
305                    List<String> currentNodeNames = getCurrentNodeNames();
306                    name = StringUtils.join(currentNodeNames, CURRENT_ROUTE_NODE_NAME_DELIMITER);
307            }
308            return name;
309        }
310    
311        public List<String> getCurrentNodeNames() {
312            return KEWServiceLocator.getRouteNodeService().getCurrentRouteNodeNames(getDocumentId());
313        }
314    
315        public String getRouteStatusLabel() {
316            return CodeTranslator.getRouteStatusLabel(getDocRouteStatus());
317        }
318    
319        public String getDocRouteStatusLabel() {
320            return CodeTranslator.getRouteStatusLabel(getDocRouteStatus());
321        }
322        /**
323         * 
324         * This method returns the Document Status Policy for the document type associated with this Route Header.
325         * The Document Status Policy denotes whether the KEW Route Status, or the Application Document Status,
326         * or both are to be displayed.
327         * 
328         * @return
329         */
330        public String getDocStatusPolicy() {
331            return getDocumentType().getDocumentStatusPolicy().getPolicyStringValue();
332        }
333    
334        public Collection getQueueItems() {
335            return queueItems;
336        }
337    
338        public void setQueueItems(Collection queueItems) {
339            this.queueItems = queueItems;
340        }
341    
342        public List<ActionItem> getActionItems() {
343            return (List<ActionItem>) KEWServiceLocator.getActionListService().findByDocumentId(documentId);
344        }
345    
346        public List<ActionTakenValue> getActionsTaken() {
347           return (List<ActionTakenValue>) KEWServiceLocator.getActionTakenService().findByDocumentIdIgnoreCurrentInd(documentId);
348        }
349    
350        public List<ActionRequestValue> getActionRequests() {
351            if (this.simulatedActionRequests == null || this.simulatedActionRequests.isEmpty()) {
352                return KEWServiceLocator.getActionRequestService().findByDocumentIdIgnoreCurrentInd(documentId);
353            } else {
354                return this.simulatedActionRequests;
355            }
356        }
357    
358        public List<ActionRequestValue> getSimulatedActionRequests() {
359            if (this.simulatedActionRequests == null) {
360                this.simulatedActionRequests = new ArrayList<ActionRequestValue>();
361            }
362            return this.simulatedActionRequests;
363        }
364    
365        public void setSimulatedActionRequests(List<ActionRequestValue> simulatedActionRequests) {
366            this.simulatedActionRequests = simulatedActionRequests;
367        }
368    
369        public DocumentType getDocumentType() {
370            return KEWServiceLocator.getDocumentTypeService().findById(getDocumentTypeId());
371        }
372    
373        public java.lang.String getAppDocId() {
374            return appDocId;
375        }
376    
377        public void setAppDocId(java.lang.String appDocId) {
378            this.appDocId = appDocId;
379        }
380    
381        public java.sql.Timestamp getApprovedDate() {
382            return approvedDate;
383        }
384    
385        public void setApprovedDate(java.sql.Timestamp approvedDate) {
386            this.approvedDate = approvedDate;
387        }
388    
389        public java.sql.Timestamp getCreateDate() {
390            return createDate;
391        }
392    
393        public void setCreateDate(java.sql.Timestamp createDate) {
394            this.createDate = createDate;
395        }
396    
397        public java.lang.String getDocContent() {
398            return getDocumentContent().getDocumentContent();
399        }
400    
401        public void setDocContent(java.lang.String docContent) {
402            DocumentRouteHeaderValueContent content = getDocumentContent();
403            content.setDocumentContent(docContent);
404        }
405    
406        public java.lang.Integer getDocRouteLevel() {
407            return docRouteLevel;
408        }
409    
410        public void setDocRouteLevel(java.lang.Integer docRouteLevel) {
411            this.docRouteLevel = docRouteLevel;
412        }
413    
414        public java.lang.String getDocRouteStatus() {
415            return docRouteStatus;
416        }
417    
418        public void setDocRouteStatus(java.lang.String docRouteStatus) {
419            this.docRouteStatus = docRouteStatus;
420        }
421    
422        public java.lang.String getDocTitle() {
423            return docTitle;
424        }
425    
426        public void setDocTitle(java.lang.String docTitle) {
427            this.docTitle = docTitle;
428        }
429    
430        @Override
431        public String getDocumentTypeId() {
432            return documentTypeId;
433        }
434    
435        public void setDocumentTypeId(String documentTypeId) {
436            this.documentTypeId = documentTypeId;
437        }
438    
439        public java.lang.Integer getDocVersion() {
440            return docVersion;
441        }
442    
443        public void setDocVersion(java.lang.Integer docVersion) {
444            this.docVersion = docVersion;
445        }
446    
447        public java.sql.Timestamp getFinalizedDate() {
448            return finalizedDate;
449        }
450    
451        public void setFinalizedDate(java.sql.Timestamp finalizedDate) {
452            this.finalizedDate = finalizedDate;
453        }
454    
455        public java.lang.String getInitiatorWorkflowId() {
456            return initiatorWorkflowId;
457        }
458    
459        public void setInitiatorWorkflowId(java.lang.String initiatorWorkflowId) {
460            this.initiatorWorkflowId = initiatorWorkflowId;
461        }
462    
463        public java.lang.String getRoutedByUserWorkflowId() {
464            if ( (isEnroute()) && (StringUtils.isBlank(routedByUserWorkflowId)) ) {
465                return initiatorWorkflowId;
466            }
467            return routedByUserWorkflowId;
468        }
469    
470        public void setRoutedByUserWorkflowId(java.lang.String routedByUserWorkflowId) {
471            this.routedByUserWorkflowId = routedByUserWorkflowId;
472        }
473    
474        @Override
475        public String getDocumentId() {
476            return documentId;
477        }
478    
479        public void setDocumentId(java.lang.String documentId) {
480            this.documentId = documentId;
481        }
482    
483        public java.sql.Timestamp getRouteStatusDate() {
484            return routeStatusDate;
485        }
486    
487        public void setRouteStatusDate(java.sql.Timestamp routeStatusDate) {
488            this.routeStatusDate = routeStatusDate;
489        }
490    
491        public java.sql.Timestamp getDateModified() {
492            return dateModified;
493        }
494    
495        public void setDateModified(java.sql.Timestamp dateModified) {
496            this.dateModified = dateModified;
497        }
498    
499        /**
500         * 
501         * This method returns the Application Document Status.
502         * This status is an alternative to the Route Status that may be used for a document.
503         * It is configurable per document type.
504         * 
505         * @see ApplicationDocumentStatus
506         * @see DocumentTypePolicy
507         * 
508         * @return
509         */
510        public java.lang.String getAppDocStatus() {
511            if (appDocStatus == null || "".equals(appDocStatus)){
512                return KewApiConstants.UNKNOWN_STATUS;
513            }
514            return appDocStatus;
515        }
516    
517        public void setAppDocStatus(java.lang.String appDocStatus){
518            this.appDocStatus = appDocStatus;
519        }
520    
521        /**
522         * 
523         * This method returns a combination of the route status label and the app doc status.
524         * 
525         * @return
526         */
527        public String getCombinedStatus(){
528            String routeStatus = getRouteStatusLabel();
529            String appStatus = getAppDocStatus();
530            if (routeStatus != null && routeStatus.length()>0){
531                if (appStatus.length() > 0){
532                    routeStatus += ", "+appStatus;
533                }
534            } else {
535                return appStatus;
536            }
537            return routeStatus;
538        }
539    
540        /**
541         * 
542         * This method sets the appDocStatus.
543         * It firsts validates the new value against the defined acceptable values, if defined.
544         * It also updates the AppDocStatus date, and saves the status transition information
545         * 
546         * @param appDocStatus
547         * @throws WorkflowRuntimeException
548         */
549        public void updateAppDocStatus(java.lang.String appDocStatus) throws WorkflowRuntimeException{
550            //validate against allowable values if defined
551            if (appDocStatus != null && appDocStatus.length() > 0 && !appDocStatus.equalsIgnoreCase(this.appDocStatus)){
552                DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findById(this.getDocumentTypeId());
553                if (documentType.getValidApplicationStatuses() != null  && documentType.getValidApplicationStatuses().size() > 0){
554                    Iterator<ApplicationDocumentStatus> iter = documentType.getValidApplicationStatuses().iterator();
555                    boolean statusValidated = false;
556                    while (iter.hasNext())
557                    {
558                        ApplicationDocumentStatus myAppDocStat = iter.next();
559                        if (appDocStatus.compareToIgnoreCase(myAppDocStat.getStatusName()) == 0)
560                        {
561                            statusValidated = true;
562                            break;
563                        }
564                    }
565                    if (!statusValidated){
566                        WorkflowRuntimeException xpee = new WorkflowRuntimeException("AppDocStatus value " +  appDocStatus + " not allowable.");
567                        LOG.error("Error validating nextAppDocStatus name: " +  appDocStatus + " against acceptable values.", xpee);
568                        throw xpee;
569                    }
570                }
571    
572                // set the status value
573                String oldStatus = this.appDocStatus;
574                this.appDocStatus = appDocStatus;
575    
576                // update the timestamp
577                setAppDocStatusDate(new Timestamp(System.currentTimeMillis()));
578    
579                // save the status transition
580                this.appDocStatusHistory.add(new DocumentStatusTransition(documentId, oldStatus, appDocStatus));
581            }
582    
583        }
584    
585    
586        public java.sql.Timestamp getAppDocStatusDate() {
587            return appDocStatusDate;
588        }
589    
590        public void setAppDocStatusDate(java.sql.Timestamp appDocStatusDate) {
591            this.appDocStatusDate = appDocStatusDate;
592        }
593    
594        public Object copy(boolean preserveKeys) {
595            throw new UnsupportedOperationException("The copy method is deprecated and unimplemented!");
596        }
597    
598        /**
599         * @return True if the document is in the state of Initiated
600         */
601        public boolean isStateInitiated() {
602            return KewApiConstants.ROUTE_HEADER_INITIATED_CD.equals(docRouteStatus);
603        }
604    
605        /**
606         * @return True if the document is in the state of Saved
607         */
608        public boolean isStateSaved() {
609            return KewApiConstants.ROUTE_HEADER_SAVED_CD.equals(docRouteStatus);
610        }
611    
612        /**
613         * @return true if the document has ever been inte enroute state
614         */
615        public boolean isRouted() {
616            return !(isStateInitiated() || isStateSaved());
617        }
618    
619        public boolean isInException() {
620            return KewApiConstants.ROUTE_HEADER_EXCEPTION_CD.equals(docRouteStatus);
621        }
622    
623        public boolean isDisaproved() {
624            return KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(docRouteStatus);
625        }
626    
627        public boolean isCanceled() {
628            return KewApiConstants.ROUTE_HEADER_CANCEL_CD.equals(docRouteStatus);
629        }
630    
631        public boolean isFinal() {
632            return KewApiConstants.ROUTE_HEADER_FINAL_CD.equals(docRouteStatus);
633        }
634    
635        public boolean isEnroute() {
636            return KewApiConstants.ROUTE_HEADER_ENROUTE_CD.equals(docRouteStatus);
637        }
638    
639        /**
640         * @return true if the document is in the processed state
641         */
642        public boolean isProcessed() {
643            return KewApiConstants.ROUTE_HEADER_PROCESSED_CD.equals(docRouteStatus);
644        }
645    
646        public boolean isRoutable() {
647            return KewApiConstants.ROUTE_HEADER_ENROUTE_CD.equals(docRouteStatus) ||
648                    //KewApiConstants.ROUTE_HEADER_EXCEPTION_CD.equals(docRouteStatus) ||
649                    KewApiConstants.ROUTE_HEADER_SAVED_CD.equals(docRouteStatus) ||
650                    KewApiConstants.ROUTE_HEADER_PROCESSED_CD.equals(docRouteStatus);
651        }
652    
653        /**
654         * Return true if the given action code is valid for this document's current state.
655         * This method only verifies statically defined action/state transitions, it does not
656         * perform full action validation logic.
657         * @see org.kuali.rice.kew.actions.ActionRegistry#getValidActions(org.kuali.rice.kim.api.identity.principal.PrincipalContract, DocumentRouteHeaderValue)
658         * @param actionCd The action code to be tested.
659         * @return True if the action code is valid for the document's status.
660         */
661        public boolean isValidActionToTake(String actionCd) {
662            String actions = (String) legalActions.get(docRouteStatus);
663            return actions.contains(actionCd);
664        }
665    
666        public boolean isValidStatusChange(String newStatus) {
667            return ((String) stateTransitionMap.get(getDocRouteStatus())).contains(newStatus);
668        }
669    
670        public void setRouteStatus(String newStatus, boolean finalState) throws InvalidActionTakenException {
671            if (newStatus != getDocRouteStatus()) {
672                // only modify the status mod date if the status actually changed
673                setRouteStatusDate(new Timestamp(System.currentTimeMillis()));
674            }
675            if (((String) stateTransitionMap.get(getDocRouteStatus())).contains(newStatus)) {
676                LOG.debug("changing status");
677                setDocRouteStatus(newStatus);
678            } else {
679                LOG.debug("unable to change status");
680                throw new InvalidActionTakenException("Document status " + CodeTranslator.getRouteStatusLabel(getDocRouteStatus()) + " cannot transition to status " + CodeTranslator
681                        .getRouteStatusLabel(newStatus));
682            }
683            setDateModified(new Timestamp(System.currentTimeMillis()));
684            if (finalState) {
685                LOG.debug("setting final timeStamp");
686                setFinalizedDate(new Timestamp(System.currentTimeMillis()));
687            }
688        }
689    
690        /**
691         * Mark the document as being processed.
692         *
693         * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
694         * @throws InvalidActionTakenException
695         */
696        public void markDocumentProcessed() throws InvalidActionTakenException {
697            LOG.debug(this + " marked processed");
698            setRouteStatus(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, !FINAL_STATE);
699        }
700    
701        /**
702         * Mark document cancled.
703         *
704         * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
705         * @throws InvalidActionTakenException
706         */
707        public void markDocumentCanceled() throws InvalidActionTakenException {
708            LOG.debug(this + " marked canceled");
709            setRouteStatus(KewApiConstants.ROUTE_HEADER_CANCEL_CD, FINAL_STATE);
710        }
711        
712        /**
713         * Mark document recalled.
714         *
715         * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
716         * @throws InvalidActionTakenException
717         */
718        public void markDocumentRecalled() throws InvalidActionTakenException {
719            LOG.debug(this + " marked recalled");
720            setRouteStatus(DocumentStatus.RECALLED.getCode(), FINAL_STATE);
721        }
722        
723        /**
724         * Mark document disapproved
725         *
726         * @throws ResourceUnavailableException
727         * @throws InvalidActionTakenException
728         */
729        public void markDocumentDisapproved() throws InvalidActionTakenException {
730            LOG.debug(this + " marked disapproved");
731            setRouteStatus(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, FINAL_STATE);
732        }
733    
734        /**
735         * Mark document saved
736         *
737         * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
738         * @throws InvalidActionTakenException
739         */
740        public void markDocumentSaved() throws InvalidActionTakenException {
741            LOG.debug(this + " marked saved");
742            setRouteStatus(KewApiConstants.ROUTE_HEADER_SAVED_CD, !FINAL_STATE);
743        }
744    
745        /**
746         * Mark the document as being in the exception state.
747         *
748         * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
749         * @throws InvalidActionTakenException
750         */
751        public void markDocumentInException() throws InvalidActionTakenException {
752            LOG.debug(this + " marked in exception");
753            setRouteStatus(KewApiConstants.ROUTE_HEADER_EXCEPTION_CD, !FINAL_STATE);
754        }
755    
756        /**
757         * Mark the document as being actively routed.
758         *
759         * @throws ResourceUnavailableException
760         * @throws InvalidActionTakenException
761         */
762        public void markDocumentEnroute() throws InvalidActionTakenException {
763            LOG.debug(this + " marked enroute");
764            setRouteStatus(KewApiConstants.ROUTE_HEADER_ENROUTE_CD, !FINAL_STATE);
765        }
766    
767        /**
768         * Mark document finalized.
769         *
770         * @throws ResourceUnavailableException
771         * @throws InvalidActionTakenException
772         */
773        public void markDocumentFinalized() throws InvalidActionTakenException {
774            LOG.debug(this + " marked finalized");
775            setRouteStatus(KewApiConstants.ROUTE_HEADER_FINAL_CD, FINAL_STATE);
776        }
777    
778        /**
779         * This method takes data from a VO and sets it on this route header
780         * @param routeHeaderVO
781         * @throws org.kuali.rice.kew.api.exception.WorkflowException
782         */
783        public void setRouteHeaderData(Document routeHeaderVO) throws WorkflowException {
784            if (!ObjectUtils.equals(getDocTitle(), routeHeaderVO.getTitle())) {
785                KEWServiceLocator.getActionListService().updateActionItemsForTitleChange(getDocumentId(), routeHeaderVO.getTitle());
786            }
787            setDocTitle(routeHeaderVO.getTitle());
788            setAppDocId(routeHeaderVO.getApplicationDocumentId());
789            setDateModified(new Timestamp(System.currentTimeMillis()));
790            updateAppDocStatus(routeHeaderVO.getApplicationDocumentStatus());
791    
792            /* set the variables from the routeHeaderVO */
793            for (Map.Entry<String, String> kvp : routeHeaderVO.getVariables().entrySet()) {
794                setVariable(kvp.getKey(), kvp.getValue());
795            }
796        }
797    
798        public void applyDocumentUpdate(DocumentUpdate documentUpdate) {
799            if (documentUpdate != null) {
800                String thisDocTitle = getDocTitle() == null ? "" : getDocTitle();
801                String updateDocTitle = documentUpdate.getTitle() == null ? "" : documentUpdate.getTitle();
802                if (!StringUtils.equals(thisDocTitle, updateDocTitle)) {
803                    KEWServiceLocator.getActionListService().updateActionItemsForTitleChange(getDocumentId(), documentUpdate.getTitle());
804                }
805                setDocTitle(updateDocTitle);
806                setAppDocId(documentUpdate.getApplicationDocumentId());
807                setDateModified(new Timestamp(System.currentTimeMillis()));
808                updateAppDocStatus(documentUpdate.getApplicationDocumentStatus());
809    
810                Map<String, String> variables = documentUpdate.getVariables();
811                for (String variableName : variables.keySet()) {
812                    setVariable(variableName, variables.get(variableName));
813                }
814            }
815        }
816    
817        /**
818         * Convenience method that returns the branch of the first (and presumably only?) initial node
819         * @return the branch of the first (and presumably only?) initial node
820         */
821        public Branch getRootBranch() {
822            if (!this.initialRouteNodeInstances.isEmpty()) {
823                return ((RouteNodeInstance) getInitialRouteNodeInstance(0)).getBranch();
824            } 
825            return null;
826        }
827    
828        /**
829         * Looks up a variable (embodied in a "BranchState" key/value pair) in the
830         * branch state table.
831         */
832        private BranchState findVariable(String name) {
833            Branch rootBranch = getRootBranch();
834            if (rootBranch != null) {
835                List<BranchState> branchState = rootBranch.getBranchState();
836                Iterator<BranchState> it = branchState.iterator();
837                while (it.hasNext()) {
838                    BranchState state = it.next();
839                    if (ObjectUtils.equals(state.getKey(), BranchState.VARIABLE_PREFIX + name)) {
840                        return state;
841                    }
842                }
843            }
844            return null;
845        }
846    
847        /**
848         * Gets a variable
849         * @param name variable name
850         * @return variable value, or null if variable is not defined
851         */
852        public String getVariable(String name) {
853            BranchState state = findVariable(name);
854            if (state == null) {
855                LOG.debug("Variable not found: '" + name + "'");
856                return null;
857            }
858            return state.getValue();
859        }
860    
861        public void removeVariableThatContains(String name) {
862        List<BranchState> statesToRemove = new ArrayList<BranchState>();
863        for (BranchState state : this.getRootBranchState()) {
864            if (state.getKey().contains(name)) {
865            statesToRemove.add(state);
866            }
867        }
868        this.getRootBranchState().removeAll(statesToRemove);
869        }
870    
871        /**
872         * Sets a variable
873         * @param name variable name
874         * @param value variable value, or null if variable should be removed
875         */
876        public void setVariable(String name, String value) {
877            BranchState state = findVariable(name);
878            Branch rootBranch = getRootBranch();
879            if (rootBranch != null) {
880                List<BranchState> branchState = rootBranch.getBranchState();
881                if (state == null) {
882                    if (value == null) {
883                        LOG.debug("set non existent variable '" + name + "' to null value");
884                        return;
885                    }
886                    LOG.debug("Adding branch state: '" + name + "'='" + value + "'");
887                    state = new BranchState();
888                    state.setBranch(rootBranch);
889                    state.setKey(BranchState.VARIABLE_PREFIX + name);
890                    state.setValue(value);
891                    rootBranch.addBranchState(state);
892                } else {
893                    if (value == null) {
894                        LOG.debug("Removing value: " + state.getKey() + "=" + state.getValue());
895                        branchState.remove(state);
896                    } else {
897                        LOG.debug("Setting value of variable '" + name + "' to '" + value + "'");
898                        state.setValue(value);
899                    }
900                }
901            }
902        }
903    
904        public List<BranchState> getRootBranchState() {
905            if (this.getRootBranch() != null) {
906                return this.getRootBranch().getBranchState();
907            }
908            return null;
909        }
910    
911        public CustomActionListAttribute getCustomActionListAttribute() throws WorkflowException {
912            CustomActionListAttribute customActionListAttribute = null;
913            if (this.getDocumentType() != null) {
914                customActionListAttribute = this.getDocumentType().getCustomActionListAttribute();
915                if (customActionListAttribute != null) {
916                    return customActionListAttribute;
917                }
918            }
919            customActionListAttribute = new DefaultCustomActionListAttribute();
920            return customActionListAttribute;
921        }
922    
923        public CustomEmailAttribute getCustomEmailAttribute() throws WorkflowException {
924            CustomEmailAttribute customEmailAttribute = null;
925            try {
926                if (this.getDocumentType() != null) {
927                    customEmailAttribute = this.getDocumentType().getCustomEmailAttribute();
928                    if (customEmailAttribute != null) {
929                        customEmailAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this));
930                        return customEmailAttribute;
931                    }
932                }
933            } catch (Exception e) {
934                LOG.debug("Error in retrieving custom email attribute", e);
935            }
936            customEmailAttribute = new CustomEmailAttributeImpl();
937            customEmailAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this));
938            return customEmailAttribute;
939        }
940    
941        public CustomNoteAttribute getCustomNoteAttribute() throws WorkflowException
942        {
943            CustomNoteAttribute customNoteAttribute = null;
944            try {
945                if (this.getDocumentType() != null) {
946                    customNoteAttribute = this.getDocumentType().getCustomNoteAttribute();
947                    if (customNoteAttribute != null) {
948                        customNoteAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this));
949                        return customNoteAttribute;
950                    }
951                }
952            } catch (Exception e) {
953                LOG.debug("Error in retrieving custom note attribute", e);
954            }
955            customNoteAttribute = new CustomNoteAttributeImpl();
956            customNoteAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this));
957            return customNoteAttribute;
958        }
959    
960        public ActionRequestValue getDocActionRequest(int index) {
961            List<ActionRequestValue> actionRequests = getActionRequests();
962            while (actionRequests.size() <= index) {
963                ActionRequestValue actionRequest = new ActionRequestFactory(this).createBlankActionRequest();
964                actionRequest.setNodeInstance(new RouteNodeInstance());
965                actionRequests.add(actionRequest);
966            }
967            return (ActionRequestValue) actionRequests.get(index);
968        }
969    
970        public ActionTakenValue getDocActionTaken(int index) {
971            List<ActionTakenValue> actionsTaken = getActionsTaken();
972            while (actionsTaken.size() <= index) {
973                actionsTaken.add(new ActionTakenValue());
974            }
975            return (ActionTakenValue) actionsTaken.get(index);
976        }
977    
978        public ActionItem getDocActionItem(int index) {
979            List<ActionItem> actionItems = getActionItems();
980            while (actionItems.size() <= index) {
981                actionItems.add(new ActionItem());
982            }
983            return (ActionItem) actionItems.get(index);
984        }
985    
986        private RouteNodeInstance getInitialRouteNodeInstance(int index) {
987            if (initialRouteNodeInstances.size() >= index) {
988                return (RouteNodeInstance) initialRouteNodeInstances.get(index);
989            }
990            return null;
991        }
992    
993    //      /**
994    //       * @param searchableAttributeValues The searchableAttributeValues to set.
995    //       */
996    //      public void setSearchableAttributeValues(List<SearchableAttributeValue> searchableAttributeValues) {
997    //              this.searchableAttributeValues = searchableAttributeValues;
998    //      }
999    //
1000    //      /**
1001    //       * @return Returns the searchableAttributeValues.
1002    //       */
1003    //      public List<SearchableAttributeValue> getSearchableAttributeValues() {
1004    //              return searchableAttributeValues;
1005    //      }
1006    
1007        public boolean isRoutingReport() {
1008            return routingReport;
1009        }
1010    
1011        public void setRoutingReport(boolean routingReport) {
1012            this.routingReport = routingReport;
1013        }
1014    
1015        public List<RouteNodeInstance> getInitialRouteNodeInstances() {
1016            return initialRouteNodeInstances;
1017        }
1018    
1019        public void setInitialRouteNodeInstances(List<RouteNodeInstance> initialRouteNodeInstances) {
1020            this.initialRouteNodeInstances = initialRouteNodeInstances;
1021        }
1022    
1023        public List<Note> getNotes() {
1024            return notes;
1025        }
1026    
1027        public void setNotes(List<Note> notes) {
1028            this.notes = notes;
1029        }
1030    
1031        public DocumentRouteHeaderValueContent getDocumentContent() {
1032            if (documentContent == null) {
1033                documentContent = KEWServiceLocator.getRouteHeaderService().getContent(getDocumentId());
1034            }
1035            return documentContent;
1036        }
1037    
1038        public void setDocumentContent(DocumentRouteHeaderValueContent documentContent) {
1039            this.documentContent = documentContent;
1040        }
1041    
1042        public List<DocumentStatusTransition> getAppDocStatusHistory() {
1043            return this.appDocStatusHistory;
1044        }
1045    
1046        public void setAppDocStatusHistory(
1047                List<DocumentStatusTransition> appDocStatusHistory) {
1048            this.appDocStatusHistory = appDocStatusHistory;
1049        }
1050    
1051        @Override
1052        public DocumentStatus getStatus() {
1053            return DocumentStatus.fromCode(getDocRouteStatus());
1054        }
1055    
1056        @Override
1057        public DateTime getDateCreated() {
1058            if (getCreateDate() == null) {
1059                return null;
1060            }
1061            return new DateTime(getCreateDate().getTime());
1062        }
1063    
1064        @Override
1065        public DateTime getDateLastModified() {
1066            if (getDateModified() == null) {
1067                return null;
1068            }
1069            return new DateTime(getDateModified().getTime());
1070        }
1071    
1072        @Override
1073        public DateTime getDateApproved() {
1074            if (getApprovedDate() == null) {
1075                return null;
1076            }
1077            return new DateTime(getApprovedDate().getTime());
1078        }
1079    
1080        @Override
1081        public DateTime getDateFinalized() {
1082            if (getFinalizedDate() == null) {
1083                return null;
1084            }
1085            return new DateTime(getFinalizedDate().getTime());
1086        }
1087    
1088        @Override
1089        public String getTitle() {
1090            return docTitle;
1091        }
1092    
1093        @Override
1094        public String getApplicationDocumentId() {
1095            return appDocId;
1096        }
1097    
1098        @Override
1099        public String getInitiatorPrincipalId() {
1100            return initiatorWorkflowId;
1101        }
1102    
1103        @Override
1104        public String getRoutedByPrincipalId() {
1105            return routedByUserWorkflowId;
1106        }
1107    
1108        @Override
1109        public String getDocumentTypeName() {
1110            return getDocumentType().getName();
1111        }
1112    
1113        @Override
1114        public String getDocumentHandlerUrl() {
1115            return getDocumentType().getResolvedDocumentHandlerUrl();
1116        }
1117    
1118        @Override
1119        public String getApplicationDocumentStatus() {
1120            return appDocStatus;
1121        }
1122    
1123        @Override
1124        public DateTime getApplicationDocumentStatusDate() {
1125            if (appDocStatusDate == null) {
1126                return null;
1127            }
1128            return new DateTime(appDocStatusDate.getTime());
1129        }
1130    
1131        @Override
1132        public Map<String, String> getVariables() {
1133            Map<String, String> documentVariables = new HashMap<String, String>();
1134            /* populate the routeHeaderVO with the document variables */
1135            // FIXME: we assume there is only one for now
1136            Branch routeNodeInstanceBranch = getRootBranch();
1137            // Ok, we are using the "branch state" as the arbitrary convenient repository for flow/process/edoc variables
1138            // so we need to stuff them into the VO
1139            if (routeNodeInstanceBranch != null) {
1140                List<BranchState> listOfBranchStates = routeNodeInstanceBranch.getBranchState();
1141                for (BranchState bs : listOfBranchStates) {
1142                    if (bs.getKey() != null && bs.getKey().startsWith(BranchState.VARIABLE_PREFIX)) {
1143                        LOG.debug("Setting branch state variable on vo: " + bs.getKey() + "=" + bs.getValue());
1144                        documentVariables.put(bs.getKey().substring(BranchState.VARIABLE_PREFIX.length()), bs.getValue());
1145                    }
1146                }
1147            }
1148            return documentVariables;
1149        }
1150    
1151        public static Document to(DocumentRouteHeaderValue documentBo) {
1152            if (documentBo == null) {
1153                return null;
1154            }
1155            Document.Builder builder = Document.Builder.create(documentBo);
1156            return builder.build();
1157        }
1158    
1159        public static DocumentRouteHeaderValue from(Document document) {
1160            DocumentRouteHeaderValue documentBo = new DocumentRouteHeaderValue();
1161            documentBo.setAppDocId(document.getApplicationDocumentId());
1162            if (document.getDateApproved() != null) {
1163                documentBo.setApprovedDate(new Timestamp(document.getDateApproved().getMillis()));
1164            }
1165            if (document.getDateCreated() != null) {
1166                documentBo.setCreateDate(new Timestamp(document.getDateCreated().getMillis()));
1167            }
1168            if (StringUtils.isEmpty(documentBo.getDocContent())) {
1169                documentBo.setDocContent(KewApiConstants.DEFAULT_DOCUMENT_CONTENT);
1170            }
1171            documentBo.setDocRouteStatus(document.getStatus().getCode());
1172            documentBo.setDocTitle(document.getTitle());
1173            if (document.getDocumentTypeName() != null) {
1174                DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(document.getDocumentTypeName());
1175                if (documentType == null) {
1176                    throw new RiceRuntimeException("Could not locate the given document type name: " + document.getDocumentTypeName());
1177                }
1178                documentBo.setDocumentTypeId(documentType.getDocumentTypeId());
1179            }
1180            if (document.getDateFinalized() != null) {
1181                documentBo.setFinalizedDate(new Timestamp(document.getDateFinalized().getMillis()));
1182            }
1183            documentBo.setInitiatorWorkflowId(document.getInitiatorPrincipalId());
1184            documentBo.setRoutedByUserWorkflowId(document.getRoutedByPrincipalId());
1185            documentBo.setDocumentId(document.getDocumentId());
1186            if (document.getDateLastModified() != null) {
1187                documentBo.setDateModified(new Timestamp(document.getDateLastModified().getMillis()));
1188            }
1189            documentBo.setAppDocStatus(document.getApplicationDocumentStatus());
1190            if (document.getApplicationDocumentStatusDate() != null) {
1191                documentBo.setAppDocStatusDate(new Timestamp(document.getApplicationDocumentStatusDate().getMillis()));
1192            }
1193    
1194    
1195            // Convert the variables
1196            Map<String, String> variables = document.getVariables();
1197            if( variables != null && !variables.isEmpty()){
1198                for(Map.Entry<String, String> kvp : variables.entrySet()){
1199                    documentBo.setVariable(kvp.getKey(), kvp.getValue());
1200                }
1201            }
1202    
1203            return documentBo;
1204        }
1205    
1206    }