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