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