View Javadoc

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