View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.routeheader;
17  
18  import org.apache.commons.lang.ObjectUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.hibernate.annotations.Fetch;
22  import org.hibernate.annotations.FetchMode;
23  import org.hibernate.annotations.GenericGenerator;
24  import org.hibernate.annotations.Parameter;
25  import org.joda.time.DateTime;
26  import org.kuali.rice.core.api.exception.RiceRuntimeException;
27  import org.kuali.rice.kew.actionitem.ActionItem;
28  import org.kuali.rice.kew.actionlist.CustomActionListAttribute;
29  import org.kuali.rice.kew.actionlist.DefaultCustomActionListAttribute;
30  import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
31  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
32  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
33  import org.kuali.rice.kew.api.KewApiConstants;
34  import org.kuali.rice.kew.api.WorkflowRuntimeException;
35  import org.kuali.rice.kew.api.action.ActionType;
36  import org.kuali.rice.kew.api.document.Document;
37  import org.kuali.rice.kew.api.document.DocumentContract;
38  import org.kuali.rice.kew.api.document.DocumentStatus;
39  import org.kuali.rice.kew.api.document.DocumentUpdate;
40  import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
41  import org.kuali.rice.kew.api.exception.WorkflowException;
42  import org.kuali.rice.kew.api.util.CodeTranslator;
43  import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaEbo;
44  import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
45  import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
46  import org.kuali.rice.kew.doctype.DocumentTypePolicy;
47  import org.kuali.rice.kew.doctype.bo.DocumentType;
48  import org.kuali.rice.kew.engine.CompatUtils;
49  import org.kuali.rice.kew.engine.node.Branch;
50  import org.kuali.rice.kew.engine.node.BranchState;
51  import org.kuali.rice.kew.engine.node.RouteNode;
52  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
53  import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
54  import org.kuali.rice.kew.mail.CustomEmailAttribute;
55  import org.kuali.rice.kew.mail.CustomEmailAttributeImpl;
56  import org.kuali.rice.kew.notes.CustomNoteAttribute;
57  import org.kuali.rice.kew.notes.CustomNoteAttributeImpl;
58  import org.kuali.rice.kew.notes.Note;
59  import org.kuali.rice.kew.service.KEWServiceLocator;
60  import org.kuali.rice.kim.api.identity.principal.Principal;
61  import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
62  
63  import javax.persistence.CascadeType;
64  import javax.persistence.Column;
65  import javax.persistence.Entity;
66  import javax.persistence.FetchType;
67  import javax.persistence.GeneratedValue;
68  import javax.persistence.Id;
69  import javax.persistence.JoinColumn;
70  import javax.persistence.JoinTable;
71  import javax.persistence.ManyToMany;
72  import javax.persistence.NamedQueries;
73  import javax.persistence.NamedQuery;
74  import javax.persistence.OneToMany;
75  import javax.persistence.OrderBy;
76  import javax.persistence.Table;
77  import javax.persistence.Transient;
78  import java.sql.Timestamp;
79  import java.util.ArrayList;
80  import java.util.Collection;
81  import java.util.HashMap;
82  import java.util.Iterator;
83  import java.util.List;
84  import java.util.Map;
85  
86  
87  
88  /**
89   * A document within KEW.  A document effectively represents a process that moves through
90   * the workflow engine.  It is created from a particular {@link DocumentType} and follows
91   * the route path defined by that DocumentType.
92   *
93   * <p>During a document's lifecycle it progresses through a series of statuses, starting
94   * with INITIATED and moving to one of the terminal states (such as FINAL, CANCELED, etc).
95   * The list of status on a document are defined in the {@link KewApiConstants} class and
96   * include the constants starting with "ROUTE_HEADER_" and ending with "_CD".
97   *
98   * <p>Associated with the document is the document content.  The document content is XML
99   * 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 }