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