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