001/**
002 * Copyright 2005-2015 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.joda.time.DateTime;
022import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
023import org.kuali.rice.core.api.exception.RiceRuntimeException;
024import org.kuali.rice.kew.actionitem.ActionItem;
025import org.kuali.rice.kew.actionlist.CustomActionListAttribute;
026import org.kuali.rice.kew.actionlist.DefaultCustomActionListAttribute;
027import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
028import org.kuali.rice.kew.actionrequest.ActionRequestValue;
029import org.kuali.rice.kew.actiontaken.ActionTakenValue;
030import org.kuali.rice.kew.api.KewApiConstants;
031import org.kuali.rice.kew.api.WorkflowRuntimeException;
032import org.kuali.rice.kew.api.action.ActionType;
033import org.kuali.rice.kew.api.document.Document;
034import org.kuali.rice.kew.api.document.DocumentContract;
035import org.kuali.rice.kew.api.document.DocumentStatus;
036import org.kuali.rice.kew.api.document.DocumentUpdate;
037import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
038import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
039import org.kuali.rice.kew.api.exception.WorkflowException;
040import org.kuali.rice.kew.api.util.CodeTranslator;
041import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaEbo;
042import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
043import org.kuali.rice.kew.doctype.DocumentTypePolicy;
044import org.kuali.rice.kew.doctype.bo.DocumentType;
045import org.kuali.rice.kew.engine.CompatUtils;
046import org.kuali.rice.kew.engine.node.Branch;
047import org.kuali.rice.kew.engine.node.BranchState;
048import org.kuali.rice.kew.engine.node.RouteNode;
049import org.kuali.rice.kew.engine.node.RouteNodeInstance;
050import org.kuali.rice.kew.mail.CustomEmailAttribute;
051import org.kuali.rice.kew.mail.CustomEmailAttributeImpl;
052import org.kuali.rice.kew.notes.CustomNoteAttribute;
053import org.kuali.rice.kew.notes.CustomNoteAttributeImpl;
054import org.kuali.rice.kew.notes.Note;
055import org.kuali.rice.kew.quicklinks.dao.impl.QuickLinksDAOJpa;
056import org.kuali.rice.kew.routeheader.dao.impl.DocumentRouteHeaderDAOJpa;
057import org.kuali.rice.kew.service.KEWServiceLocator;
058import org.kuali.rice.kim.api.identity.Person;
059import org.kuali.rice.kim.api.identity.principal.Principal;
060import org.kuali.rice.krad.bo.DataObjectBase;
061import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
062
063import javax.persistence.Cacheable;
064import javax.persistence.CascadeType;
065import javax.persistence.Column;
066import javax.persistence.Entity;
067import javax.persistence.GeneratedValue;
068import javax.persistence.Id;
069import javax.persistence.JoinColumn;
070import javax.persistence.JoinTable;
071import javax.persistence.ManyToMany;
072import javax.persistence.NamedAttributeNode;
073import javax.persistence.NamedEntityGraph;
074import javax.persistence.NamedEntityGraphs;
075import javax.persistence.NamedQueries;
076import javax.persistence.NamedQuery;
077import javax.persistence.OneToMany;
078import javax.persistence.OrderBy;
079import javax.persistence.Table;
080import javax.persistence.Transient;
081
082import java.sql.Timestamp;
083import java.util.ArrayList;
084import java.util.HashMap;
085import java.util.Iterator;
086import java.util.List;
087import java.util.Map;
088
089
090
091/**
092 * A document within KEW.  A document effectively represents a process that moves through
093 * the workflow engine.  It is created from a particular {@link DocumentType} and follows
094 * the route path defined by that DocumentType.
095 *
096 * <p>During a document's lifecycle it progresses through a series of statuses, starting
097 * with INITIATED and moving to one of the terminal states (such as FINAL, CANCELED, etc).
098 * The list of status on a document are defined in the {@link KewApiConstants} class and
099 * 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)
156public 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}