Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.kew.routeheader; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.kuali.rice.core.api.exception.RiceRuntimeException; import org.kuali.rice.kew.actionitem.ActionItem; import org.kuali.rice.kew.actionlist.CustomActionListAttribute; import org.kuali.rice.kew.actionlist.DefaultCustomActionListAttribute; import org.kuali.rice.kew.actionrequest.ActionRequestFactory; import org.kuali.rice.kew.actionrequest.ActionRequestValue; import org.kuali.rice.kew.actiontaken.ActionTakenValue; import org.kuali.rice.kew.api.KewApiConstants; import org.kuali.rice.kew.api.WorkflowRuntimeException; import org.kuali.rice.kew.api.action.ActionType; import org.kuali.rice.kew.api.document.Document; import org.kuali.rice.kew.api.document.DocumentContract; import org.kuali.rice.kew.api.document.DocumentStatus; import org.kuali.rice.kew.api.document.DocumentUpdate; import org.kuali.rice.kew.api.exception.InvalidActionTakenException; import org.kuali.rice.kew.api.exception.ResourceUnavailableException; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kew.api.util.CodeTranslator; import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaEbo; import org.kuali.rice.kew.doctype.ApplicationDocumentStatus; import org.kuali.rice.kew.doctype.DocumentTypePolicy; import org.kuali.rice.kew.doctype.bo.DocumentType; import org.kuali.rice.kew.engine.CompatUtils; import org.kuali.rice.kew.engine.node.Branch; import org.kuali.rice.kew.engine.node.BranchState; import org.kuali.rice.kew.engine.node.RouteNode; import org.kuali.rice.kew.engine.node.RouteNodeInstance; import org.kuali.rice.kew.mail.CustomEmailAttribute; import org.kuali.rice.kew.mail.CustomEmailAttributeImpl; import org.kuali.rice.kew.notes.CustomNoteAttribute; import org.kuali.rice.kew.notes.CustomNoteAttributeImpl; import org.kuali.rice.kew.notes.Note; import org.kuali.rice.kew.quicklinks.dao.impl.QuickLinksDAOJpa; import org.kuali.rice.kew.routeheader.dao.impl.DocumentRouteHeaderDAOJpa; import org.kuali.rice.kew.service.KEWServiceLocator; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.krad.bo.DataObjectBase; import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; import javax.persistence.Transient; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * A document within KEW. A document effectively represents a process that moves through * the workflow engine. It is created from a particular {@link DocumentType} and follows * the route path defined by that DocumentType. * * <p>During a document's lifecycle it progresses through a series of statuses, starting * with INITIATED and moving to one of the terminal states (such as FINAL, CANCELED, etc). * The list of status on a document are defined in the {@link KewApiConstants} class and * include the constants starting with "ROUTE_HEADER_" and ending with "_CD". * * <p>Associated with the document is the document content. The document content is XML * which represents the content of that document. This XML content is typically used * to make routing decisions for the document. * * <p>A document has associated with it a set of {@link ActionRequestValue} object and * {@link ActionTakenValue} objects. Action Requests represent requests for user * action (such as Approve, Acknowledge, etc). Action Takens represent action that * users have performed on the document, such as approvals or cancelling of the document. * * <p>The instantiated route path of a document is defined by it's graph of * {@link RouteNodeInstance} objects. The path starts at the initial node of the document * and progresses from there following the next nodes of each node instance. The current * active nodes on the document are defined by the "active" flag on the node instance * where are not marked as "complete". * * @see DocumentType * @see ActionRequestValue * @see ActionItem * @see ActionTakenValue * @see RouteNodeInstance * @see KewApiConstants * * @author Kuali Rice Team (rice.collab@kuali.org) */ @Entity @Table(name = "KREW_DOC_HDR_T") @NamedQueries({ @NamedQuery(name = QuickLinksDAOJpa.FIND_WATCHED_DOCUMENTS_BY_INITIATOR_WORKFLOW_ID_NAME, query = QuickLinksDAOJpa.FIND_WATCHED_DOCUMENTS_BY_INITIATOR_WORKFLOW_ID_QUERY), @NamedQuery(name = DocumentRouteHeaderDAOJpa.GET_APP_DOC_ID_NAME, query = DocumentRouteHeaderDAOJpa.GET_APP_DOC_ID_QUERY), @NamedQuery(name = DocumentRouteHeaderDAOJpa.GET_APP_DOC_STATUS_NAME, query = DocumentRouteHeaderDAOJpa.GET_APP_DOC_STATUS_QUERY), @NamedQuery(name = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_HEADERS_NAME, query = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_HEADERS_QUERY), @NamedQuery(name = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_STATUS_NAME, query = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_STATUS_QUERY), @NamedQuery(name = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_ID_BY_DOC_TYPE_APP_ID_NAME, query = DocumentRouteHeaderDAOJpa.GET_DOCUMENT_ID_BY_DOC_TYPE_APP_ID_QUERY) }) @NamedEntityGraphs({ @NamedEntityGraph(name = "DocumentRouteHeaderValue.ActionListAttributesOnly", attributeNodes = { @NamedAttributeNode("approvedDate"), @NamedAttributeNode("createDate"), @NamedAttributeNode("docRouteStatus"), @NamedAttributeNode("initiatorWorkflowId"), @NamedAttributeNode("appDocStatus"), @NamedAttributeNode("documentId"), @NamedAttributeNode("docRouteLevel"), @NamedAttributeNode("documentTypeId"), @NamedAttributeNode("docVersion") }) }) public class DocumentRouteHeaderValue extends DataObjectBase implements DocumentContract, DocumentSearchCriteriaEbo { private static final long serialVersionUID = -4700736340527913220L; private static final Logger LOG = Logger.getLogger(DocumentRouteHeaderValue.class); private static final String TERMINAL = ""; private static final boolean FINAL_STATE = true; protected static final HashMap<String, String> legalActions; protected static final HashMap<String, String> stateTransitionMap; static { stateTransitionMap = new HashMap<String, String>(); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_INITIATED_CD, KewApiConstants.ROUTE_HEADER_SAVED_CD + KewApiConstants.ROUTE_HEADER_ENROUTE_CD + KewApiConstants.ROUTE_HEADER_CANCEL_CD); 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); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_ENROUTE_CD, KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD + KewApiConstants.ROUTE_HEADER_CANCEL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD + KewApiConstants.ROUTE_HEADER_EXCEPTION_CD + KewApiConstants.ROUTE_HEADER_SAVED_CD + DocumentStatus.RECALLED.getCode()); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, TERMINAL); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_CANCEL_CD, TERMINAL); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_FINAL_CD, TERMINAL); stateTransitionMap.put(DocumentStatus.RECALLED.getCode(), TERMINAL); 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); stateTransitionMap.put(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, KewApiConstants.ROUTE_HEADER_FINAL_CD + KewApiConstants.ROUTE_HEADER_PROCESSED_CD); legalActions = new HashMap<String, String>(); 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); 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); /* ACTION_TAKEN_ROUTED_CD not included in enroute state * ACTION_TAKEN_SAVED_CD removed as of version 2.4 */ 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()); /* ACTION_TAKEN_ROUTED_CD not included in exception state * ACTION_TAKEN_SAVED_CD removed as of version 2.4.2 */ 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); legalActions.put(KewApiConstants.ROUTE_HEADER_FINAL_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD); legalActions.put(KewApiConstants.ROUTE_HEADER_CANCEL_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD); legalActions.put(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD); legalActions.put(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, KewApiConstants.ACTION_TAKEN_FYI_CD + KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD + KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD); legalActions.put(DocumentStatus.RECALLED.getCode(), TERMINAL); } public static final String CURRENT_ROUTE_NODE_NAME_DELIMITER = ", "; @Id @GeneratedValue(generator = "KREW_DOC_HDR_S") @PortableSequenceGenerator(name = "KREW_DOC_HDR_S") @Column(name = "DOC_HDR_ID", nullable = false) private String documentId; @Column(name = "DOC_TYP_ID") private String documentTypeId; @Column(name = "DOC_HDR_STAT_CD", nullable = false) private String docRouteStatus; @Column(name = "RTE_LVL", nullable = false) private Integer docRouteLevel; @Column(name = "STAT_MDFN_DT", nullable = false) private Timestamp dateModified; @Column(name = "CRTE_DT", nullable = false) private Timestamp createDate; @Column(name = "APRV_DT") private Timestamp approvedDate; @Column(name = "FNL_DT") private Timestamp finalizedDate; @Column(name = "TTL") private String docTitle; @Column(name = "APP_DOC_ID") private String appDocId; @Column(name = "DOC_VER_NBR", nullable = false) private Integer docVersion = new Integer(KewApiConstants.DocumentContentVersions.NODAL); @Column(name = "INITR_PRNCPL_ID", nullable = false) private String initiatorWorkflowId; @Column(name = "RTE_PRNCPL_ID") private String routedByUserWorkflowId; @Column(name = "RTE_STAT_MDFN_DT") private Timestamp routeStatusDate; @Column(name = "APP_DOC_STAT") private String appDocStatus; @Column(name = "APP_DOC_STAT_MDFN_DT") private Timestamp appDocStatusDate; @ManyToMany(cascade = CascadeType.ALL) @JoinTable(name = "KREW_INIT_RTE_NODE_INSTN_T", joinColumns = @JoinColumn(name = "DOC_HDR_ID"), inverseJoinColumns = @JoinColumn(name = "RTE_NODE_INSTN_ID")) private List<RouteNodeInstance> initialRouteNodeInstances = new ArrayList<RouteNodeInstance>(); /** * The appDocStatusHistory keeps a list of Application Document Status transitions * for the document. It tracks the previous status, the new status, and a timestamp of the * transition for each status transition. */ @OneToMany(cascade = CascadeType.ALL, mappedBy = "documentRouteHeaderValue") @OrderBy("statusTransitionId ASC") private List<DocumentStatusTransition> appDocStatusHistory = new ArrayList<DocumentStatusTransition>(); @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "DOC_HDR_ID") @OrderBy("noteId ASC") private List<Note> notes = new ArrayList<Note>(); @Transient private DocumentRouteHeaderValueContent documentContent; @Transient private boolean routingReport = false; @Transient private List<ActionRequestValue> simulatedActionRequests; public Principal getInitiatorPrincipal() { // if we are running a simulation, there will be no initiator if (StringUtils.isBlank(getInitiatorWorkflowId())) { return null; } return KEWServiceLocator.getIdentityHelperService().getPrincipal(getInitiatorWorkflowId()); } public Principal getRoutedByPrincipal() { if (getRoutedByUserWorkflowId() == null) { return null; } return KEWServiceLocator.getIdentityHelperService().getPrincipal(getRoutedByUserWorkflowId()); } public String getInitiatorDisplayName() { return KEWServiceLocator.getIdentityHelperService().getPerson(getInitiatorWorkflowId()).getName(); } public String getRoutedByDisplayName() { return KEWServiceLocator.getIdentityHelperService().getPerson(getRoutedByUserWorkflowId()).getName(); } public String getCurrentRouteLevelName() { String name = "Not Found"; // TODO the isRouteLevelDocument junk can be ripped out if (routingReport) { name = "Routing Report"; } else if (CompatUtils.isRouteLevelDocument(this)) { int routeLevelInt = getDocRouteLevel().intValue(); if (LOG.isInfoEnabled()) { LOG.info("Getting current route level name for a Route level document: " + routeLevelInt + CURRENT_ROUTE_NODE_NAME_DELIMITER + documentId); } List<RouteNode> routeLevelNodes = CompatUtils.getRouteLevelCompatibleNodeList(getDocumentType()); if (LOG.isInfoEnabled()) { LOG.info("Route level compatible node list has " + routeLevelNodes.size() + " nodes"); } if (routeLevelInt < routeLevelNodes.size()) { name = routeLevelNodes.get(routeLevelInt).getRouteNodeName(); } } else { List<String> currentNodeNames = getCurrentNodeNames(); name = StringUtils.join(currentNodeNames, CURRENT_ROUTE_NODE_NAME_DELIMITER); } return name; } public List<String> getCurrentNodeNames() { return KEWServiceLocator.getRouteNodeService().getCurrentRouteNodeNames(getDocumentId()); } public String getRouteStatusLabel() { return CodeTranslator.getRouteStatusLabel(getDocRouteStatus()); } public String getDocRouteStatusLabel() { return CodeTranslator.getRouteStatusLabel(getDocRouteStatus()); } /** * * This method returns the Document Status Policy for the document type associated with this Route Header. * The Document Status Policy denotes whether the KEW Route Status, or the Application Document Status, * or both are to be displayed. * * @return */ public String getDocStatusPolicy() { return getDocumentType().getDocumentStatusPolicy().getPolicyStringValue(); } public List<ActionItem> getActionItems() { return (List<ActionItem>) KEWServiceLocator.getActionListService().findByDocumentId(documentId); } public List<ActionTakenValue> getActionsTaken() { return KEWServiceLocator.getActionTakenService().findByDocumentIdIgnoreCurrentInd(documentId); } public List<ActionRequestValue> getActionRequests() { if (this.simulatedActionRequests == null || this.simulatedActionRequests.isEmpty()) { return KEWServiceLocator.getActionRequestService().findByDocumentIdIgnoreCurrentInd(documentId); } else { return this.simulatedActionRequests; } } public List<ActionRequestValue> getSimulatedActionRequests() { if (this.simulatedActionRequests == null) { this.simulatedActionRequests = new ArrayList<ActionRequestValue>(); } return this.simulatedActionRequests; } public void setSimulatedActionRequests(List<ActionRequestValue> simulatedActionRequests) { this.simulatedActionRequests = simulatedActionRequests; } public DocumentType getDocumentType() { return KEWServiceLocator.getDocumentTypeService().findById(getDocumentTypeId()); } public java.lang.String getAppDocId() { return appDocId; } public void setAppDocId(java.lang.String appDocId) { this.appDocId = appDocId; } public java.sql.Timestamp getApprovedDate() { return approvedDate; } public void setApprovedDate(java.sql.Timestamp approvedDate) { this.approvedDate = approvedDate; } public java.sql.Timestamp getCreateDate() { return createDate; } public void setCreateDate(java.sql.Timestamp createDate) { this.createDate = createDate; } public java.lang.String getDocContent() { return getDocumentContent().getDocumentContent(); } public void setDocContent(java.lang.String docContent) { DocumentRouteHeaderValueContent content = getDocumentContent(); content.setDocumentContent(docContent); } public java.lang.Integer getDocRouteLevel() { return docRouteLevel; } public void setDocRouteLevel(java.lang.Integer docRouteLevel) { this.docRouteLevel = docRouteLevel; } public java.lang.String getDocRouteStatus() { return docRouteStatus; } public void setDocRouteStatus(java.lang.String docRouteStatus) { this.docRouteStatus = docRouteStatus; } public java.lang.String getDocTitle() { return docTitle; } public void setDocTitle(java.lang.String docTitle) { this.docTitle = docTitle; } @Override public String getDocumentTypeId() { return documentTypeId; } public void setDocumentTypeId(String documentTypeId) { this.documentTypeId = documentTypeId; } public java.lang.Integer getDocVersion() { return docVersion; } public void setDocVersion(java.lang.Integer docVersion) { this.docVersion = docVersion; } public java.sql.Timestamp getFinalizedDate() { return finalizedDate; } public void setFinalizedDate(java.sql.Timestamp finalizedDate) { this.finalizedDate = finalizedDate; } public java.lang.String getInitiatorWorkflowId() { return initiatorWorkflowId; } public void setInitiatorWorkflowId(java.lang.String initiatorWorkflowId) { this.initiatorWorkflowId = initiatorWorkflowId; } public java.lang.String getRoutedByUserWorkflowId() { if ((isEnroute()) && (StringUtils.isBlank(routedByUserWorkflowId))) { return initiatorWorkflowId; } return routedByUserWorkflowId; } public void setRoutedByUserWorkflowId(java.lang.String routedByUserWorkflowId) { this.routedByUserWorkflowId = routedByUserWorkflowId; } @Override public String getDocumentId() { return documentId; } public void setDocumentId(java.lang.String documentId) { this.documentId = documentId; } public java.sql.Timestamp getRouteStatusDate() { return routeStatusDate; } public void setRouteStatusDate(java.sql.Timestamp routeStatusDate) { this.routeStatusDate = routeStatusDate; } public java.sql.Timestamp getDateModified() { return dateModified; } public void setDateModified(java.sql.Timestamp dateModified) { this.dateModified = dateModified; } /** * * This method returns the Application Document Status. * This status is an alternative to the Route Status that may be used for a document. * It is configurable per document type. * * @see ApplicationDocumentStatus * @see DocumentTypePolicy * * @return */ public java.lang.String getAppDocStatus() { if (StringUtils.isBlank(appDocStatus)) { return KewApiConstants.UNKNOWN_STATUS; } return appDocStatus; } public void setAppDocStatus(java.lang.String appDocStatus) { this.appDocStatus = appDocStatus; } /** * * This method returns a combination of the route status label and the app doc status. * * @return */ public String getCombinedStatus() { String routeStatus = getRouteStatusLabel(); String appStatus = getAppDocStatus(); if (StringUtils.isNotEmpty(routeStatus)) { if (StringUtils.isNotEmpty(appStatus)) { routeStatus += ", " + appStatus; } } else { return appStatus; } return routeStatus; } /** * * This method sets the appDocStatus. * It firsts validates the new value against the defined acceptable values, if defined. * It also updates the AppDocStatus date, and saves the status transition information * * @param appDocStatus * @throws WorkflowRuntimeException */ public void updateAppDocStatus(java.lang.String appDocStatus) throws WorkflowRuntimeException { //validate against allowable values if defined if (appDocStatus != null && appDocStatus.length() > 0 && !appDocStatus.equalsIgnoreCase(this.appDocStatus)) { DocumentType documentType = KEWServiceLocator.getDocumentTypeService() .findById(this.getDocumentTypeId()); if (documentType.getValidApplicationStatuses() != null && documentType.getValidApplicationStatuses().size() > 0) { Iterator<ApplicationDocumentStatus> iter = documentType.getValidApplicationStatuses().iterator(); boolean statusValidated = false; while (iter.hasNext()) { ApplicationDocumentStatus myAppDocStat = iter.next(); if (appDocStatus.compareToIgnoreCase(myAppDocStat.getStatusName()) == 0) { statusValidated = true; break; } } if (!statusValidated) { WorkflowRuntimeException xpee = new WorkflowRuntimeException( "AppDocStatus value " + appDocStatus + " not allowable."); LOG.error("Error validating nextAppDocStatus name: " + appDocStatus + " against acceptable values.", xpee); throw xpee; } } // set the status value String oldStatus = this.appDocStatus; this.appDocStatus = appDocStatus; // update the timestamp setAppDocStatusDate(new Timestamp(System.currentTimeMillis())); // save the status transition this.appDocStatusHistory.add(new DocumentStatusTransition(documentId, oldStatus, appDocStatus)); } } public java.sql.Timestamp getAppDocStatusDate() { return appDocStatusDate; } public void setAppDocStatusDate(java.sql.Timestamp appDocStatusDate) { this.appDocStatusDate = appDocStatusDate; } public Object copy(boolean preserveKeys) { throw new UnsupportedOperationException("The copy method is deprecated and unimplemented!"); } /** * @return True if the document is in the state of Initiated */ public boolean isStateInitiated() { return KewApiConstants.ROUTE_HEADER_INITIATED_CD.equals(docRouteStatus); } /** * @return True if the document is in the state of Saved */ public boolean isStateSaved() { return KewApiConstants.ROUTE_HEADER_SAVED_CD.equals(docRouteStatus); } /** * @return true if the document has ever been inte enroute state */ public boolean isRouted() { return !(isStateInitiated() || isStateSaved()); } public boolean isInException() { return KewApiConstants.ROUTE_HEADER_EXCEPTION_CD.equals(docRouteStatus); } public boolean isDisaproved() { return KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(docRouteStatus); } public boolean isCanceled() { return KewApiConstants.ROUTE_HEADER_CANCEL_CD.equals(docRouteStatus); } public boolean isFinal() { return KewApiConstants.ROUTE_HEADER_FINAL_CD.equals(docRouteStatus); } public boolean isEnroute() { return KewApiConstants.ROUTE_HEADER_ENROUTE_CD.equals(docRouteStatus); } /** * @return true if the document is in the processed state */ public boolean isProcessed() { return KewApiConstants.ROUTE_HEADER_PROCESSED_CD.equals(docRouteStatus); } public boolean isRoutable() { return KewApiConstants.ROUTE_HEADER_ENROUTE_CD.equals(docRouteStatus) || //KewApiConstants.ROUTE_HEADER_EXCEPTION_CD.equals(docRouteStatus) || KewApiConstants.ROUTE_HEADER_SAVED_CD.equals(docRouteStatus) || KewApiConstants.ROUTE_HEADER_PROCESSED_CD.equals(docRouteStatus); } /** * Return true if the given action code is valid for this document's current state. * This method only verifies statically defined action/state transitions, it does not * perform full action validation logic. * @see org.kuali.rice.kew.actions.ActionRegistry#getValidActions(org.kuali.rice.kim.api.identity.principal.PrincipalContract, DocumentRouteHeaderValue) * @param actionCd The action code to be tested. * @return True if the action code is valid for the document's status. */ public boolean isValidActionToTake(String actionCd) { String actions = legalActions.get(docRouteStatus); return actions.contains(actionCd); } public boolean isValidStatusChange(String newStatus) { return stateTransitionMap.get(getDocRouteStatus()).contains(newStatus); } public void setRouteStatus(String newStatus, boolean finalState) throws InvalidActionTakenException { if (newStatus != getDocRouteStatus()) { // only modify the status mod date if the status actually changed setRouteStatusDate(new Timestamp(System.currentTimeMillis())); } if (stateTransitionMap.get(getDocRouteStatus()).contains(newStatus)) { LOG.debug("changing status"); setDocRouteStatus(newStatus); } else { LOG.debug("unable to change status"); throw new InvalidActionTakenException( "Document status " + CodeTranslator.getRouteStatusLabel(getDocRouteStatus()) + " cannot transition to status " + CodeTranslator.getRouteStatusLabel(newStatus)); } setDateModified(new Timestamp(System.currentTimeMillis())); if (finalState) { LOG.debug("setting final timeStamp"); setFinalizedDate(new Timestamp(System.currentTimeMillis())); } } /** * Mark the document as being processed. * * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentProcessed() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked processed"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_PROCESSED_CD, !FINAL_STATE); } /** * Mark document cancled. * * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentCanceled() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked canceled"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_CANCEL_CD, FINAL_STATE); } /** * Mark document recalled. * * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentRecalled() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked recalled"); } setRouteStatus(DocumentStatus.RECALLED.getCode(), FINAL_STATE); } /** * Mark document disapproved * * @throws ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentDisapproved() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked disapproved"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD, FINAL_STATE); } /** * Mark document saved * * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentSaved() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked saved"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_SAVED_CD, !FINAL_STATE); } /** * Mark the document as being in the exception state. * * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentInException() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked in exception"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_EXCEPTION_CD, !FINAL_STATE); } /** * Mark the document as being actively routed. * * @throws ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentEnroute() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked enroute"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_ENROUTE_CD, !FINAL_STATE); } /** * Mark document finalized. * * @throws ResourceUnavailableException * @throws InvalidActionTakenException */ public void markDocumentFinalized() throws InvalidActionTakenException { if (LOG.isDebugEnabled()) { LOG.debug(this + " marked finalized"); } setRouteStatus(KewApiConstants.ROUTE_HEADER_FINAL_CD, FINAL_STATE); } /** * This method takes data from a VO and sets it on this route header * @param routeHeaderVO * @throws org.kuali.rice.kew.api.exception.WorkflowException */ public void setRouteHeaderData(Document routeHeaderVO) throws WorkflowException { if (!ObjectUtils.equals(getDocTitle(), routeHeaderVO.getTitle())) { KEWServiceLocator.getActionListService().updateActionItemsForTitleChange(getDocumentId(), routeHeaderVO.getTitle()); } setDocTitle(routeHeaderVO.getTitle()); setAppDocId(routeHeaderVO.getApplicationDocumentId()); setDateModified(new Timestamp(System.currentTimeMillis())); updateAppDocStatus(routeHeaderVO.getApplicationDocumentStatus()); /* set the variables from the routeHeaderVO */ for (Map.Entry<String, String> kvp : routeHeaderVO.getVariables().entrySet()) { setVariable(kvp.getKey(), kvp.getValue()); } } public void applyDocumentUpdate(DocumentUpdate documentUpdate) { if (documentUpdate != null) { String thisDocTitle = getDocTitle() == null ? "" : getDocTitle(); String updateDocTitle = documentUpdate.getTitle() == null ? "" : documentUpdate.getTitle(); if (!StringUtils.equals(thisDocTitle, updateDocTitle)) { KEWServiceLocator.getActionListService().updateActionItemsForTitleChange(getDocumentId(), documentUpdate.getTitle()); } setDocTitle(updateDocTitle); setAppDocId(documentUpdate.getApplicationDocumentId()); setDateModified(new Timestamp(System.currentTimeMillis())); updateAppDocStatus(documentUpdate.getApplicationDocumentStatus()); Map<String, String> variables = documentUpdate.getVariables(); for (String variableName : variables.keySet()) { setVariable(variableName, variables.get(variableName)); } } } /** * Convenience method that returns the branch of the first (and presumably only?) initial node * @return the branch of the first (and presumably only?) initial node */ public Branch getRootBranch() { if (!this.initialRouteNodeInstances.isEmpty()) { return getInitialRouteNodeInstance(0).getBranch(); } return null; } /** * Looks up a variable (embodied in a "BranchState" key/value pair) in the * branch state table. */ private BranchState findVariable(String name) { Branch rootBranch = getRootBranch(); if (rootBranch != null) { List<BranchState> branchState = rootBranch.getBranchState(); Iterator<BranchState> it = branchState.iterator(); while (it.hasNext()) { BranchState state = it.next(); if (ObjectUtils.equals(state.getKey(), BranchState.VARIABLE_PREFIX + name)) { return state; } } } return null; } /** * Gets a variable * @param name variable name * @return variable value, or null if variable is not defined */ public String getVariable(String name) { BranchState state = findVariable(name); if (state == null) { if (LOG.isDebugEnabled()) { LOG.debug("Variable not found: '" + name + "'"); } return null; } return state.getValue(); } public void removeVariableThatContains(String name) { List<BranchState> statesToRemove = new ArrayList<BranchState>(); for (BranchState state : this.getRootBranchState()) { if (state.getKey().contains(name)) { statesToRemove.add(state); } } this.getRootBranchState().removeAll(statesToRemove); } /** * Sets a variable * @param name variable name * @param value variable value, or null if variable should be removed */ public void setVariable(String name, String value) { BranchState state = findVariable(name); Branch rootBranch = getRootBranch(); if (rootBranch != null) { List<BranchState> branchState = rootBranch.getBranchState(); if (state == null) { if (value == null) { if (LOG.isDebugEnabled()) { LOG.debug("set non existent variable '" + name + "' to null value"); } return; } LOG.debug("Adding branch state: '" + name + "'='" + value + "'"); state = new BranchState(); state.setBranch(rootBranch); state.setKey(BranchState.VARIABLE_PREFIX + name); state.setValue(value); rootBranch.addBranchState(state); } else { if (value == null) { if (LOG.isDebugEnabled()) { LOG.debug("Removing value: " + state.getKey() + "=" + state.getValue()); } branchState.remove(state); } else { if (LOG.isDebugEnabled()) { LOG.debug("Setting value of variable '" + name + "' to '" + value + "'"); } state.setValue(value); } } } } public List<BranchState> getRootBranchState() { if (this.getRootBranch() != null) { return this.getRootBranch().getBranchState(); } return null; } public CustomActionListAttribute getCustomActionListAttribute() throws WorkflowException { CustomActionListAttribute customActionListAttribute = null; if (this.getDocumentType() != null) { customActionListAttribute = this.getDocumentType().getCustomActionListAttribute(); if (customActionListAttribute != null) { return customActionListAttribute; } } customActionListAttribute = new DefaultCustomActionListAttribute(); return customActionListAttribute; } public CustomEmailAttribute getCustomEmailAttribute() throws WorkflowException { CustomEmailAttribute customEmailAttribute = null; try { if (this.getDocumentType() != null) { customEmailAttribute = this.getDocumentType().getCustomEmailAttribute(); if (customEmailAttribute != null) { customEmailAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this)); return customEmailAttribute; } } } catch (Exception e) { LOG.warn("Error in retrieving custom email attribute", e); } customEmailAttribute = new CustomEmailAttributeImpl(); customEmailAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this)); return customEmailAttribute; } public CustomNoteAttribute getCustomNoteAttribute() throws WorkflowException { CustomNoteAttribute customNoteAttribute = null; try { if (this.getDocumentType() != null) { customNoteAttribute = this.getDocumentType().getCustomNoteAttribute(); if (customNoteAttribute != null) { customNoteAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this)); return customNoteAttribute; } } } catch (Exception e) { LOG.warn("Error in retrieving custom note attribute", e); } customNoteAttribute = new CustomNoteAttributeImpl(); customNoteAttribute.setRouteHeaderVO(DocumentRouteHeaderValue.to(this)); return customNoteAttribute; } public ActionRequestValue getDocActionRequest(int index) { List<ActionRequestValue> actionRequests = getActionRequests(); while (actionRequests.size() <= index) { ActionRequestValue actionRequest = new ActionRequestFactory(this).createBlankActionRequest(); actionRequest.setNodeInstance(new RouteNodeInstance()); actionRequests.add(actionRequest); } return actionRequests.get(index); } public ActionTakenValue getDocActionTaken(int index) { List<ActionTakenValue> actionsTaken = getActionsTaken(); while (actionsTaken.size() <= index) { actionsTaken.add(new ActionTakenValue()); } return actionsTaken.get(index); } public ActionItem getDocActionItem(int index) { List<ActionItem> actionItems = getActionItems(); while (actionItems.size() <= index) { actionItems.add(new ActionItem()); } return actionItems.get(index); } private RouteNodeInstance getInitialRouteNodeInstance(int index) { if (initialRouteNodeInstances.size() >= index) { return initialRouteNodeInstances.get(index); } return null; } public boolean isRoutingReport() { return routingReport; } public void setRoutingReport(boolean routingReport) { this.routingReport = routingReport; } public List<RouteNodeInstance> getInitialRouteNodeInstances() { return initialRouteNodeInstances; } public void setInitialRouteNodeInstances(List<RouteNodeInstance> initialRouteNodeInstances) { this.initialRouteNodeInstances = initialRouteNodeInstances; } public List<Note> getNotes() { return notes; } public void setNotes(List<Note> notes) { this.notes = notes; } public DocumentRouteHeaderValueContent getDocumentContent() { if (documentContent == null) { documentContent = KEWServiceLocator.getRouteHeaderService().getContent(getDocumentId()); } return documentContent; } public void setDocumentContent(DocumentRouteHeaderValueContent documentContent) { this.documentContent = documentContent; } public List<DocumentStatusTransition> getAppDocStatusHistory() { return this.appDocStatusHistory; } public void setAppDocStatusHistory(List<DocumentStatusTransition> appDocStatusHistory) { this.appDocStatusHistory = appDocStatusHistory; } @Override public DocumentStatus getStatus() { return DocumentStatus.fromCode(getDocRouteStatus()); } @Override public DateTime getDateCreated() { if (getCreateDate() == null) { return null; } return new DateTime(getCreateDate().getTime()); } @Override public DateTime getDateLastModified() { if (getDateModified() == null) { return null; } return new DateTime(getDateModified().getTime()); } @Override public DateTime getDateApproved() { if (getApprovedDate() == null) { return null; } return new DateTime(getApprovedDate().getTime()); } @Override public DateTime getDateFinalized() { if (getFinalizedDate() == null) { return null; } return new DateTime(getFinalizedDate().getTime()); } @Override public String getTitle() { return docTitle; } @Override public String getApplicationDocumentId() { return appDocId; } @Override public String getInitiatorPrincipalId() { return initiatorWorkflowId; } @Override public String getRoutedByPrincipalId() { return routedByUserWorkflowId; } @Override public String getDocumentTypeName() { return getDocumentType().getName(); } @Override public String getDocumentHandlerUrl() { return getDocumentType().getResolvedDocumentHandlerUrl(); } @Override public String getApplicationDocumentStatus() { return appDocStatus; } @Override public DateTime getApplicationDocumentStatusDate() { if (appDocStatusDate == null) { return null; } return new DateTime(appDocStatusDate.getTime()); } @Override public Map<String, String> getVariables() { Map<String, String> documentVariables = new HashMap<String, String>(); /* populate the routeHeaderVO with the document variables */ // FIXME: we assume there is only one for now Branch routeNodeInstanceBranch = getRootBranch(); // Ok, we are using the "branch state" as the arbitrary convenient repository for flow/process/edoc variables // so we need to stuff them into the VO if (routeNodeInstanceBranch != null) { List<BranchState> listOfBranchStates = routeNodeInstanceBranch.getBranchState(); for (BranchState bs : listOfBranchStates) { if (bs.getKey() != null && bs.getKey().startsWith(BranchState.VARIABLE_PREFIX)) { if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) { LOG.debug("Setting branch state variable on vo: " + bs.getKey() + "=" + bs.getValue()); } } documentVariables.put(bs.getKey().substring(BranchState.VARIABLE_PREFIX.length()), bs.getValue()); } } } return documentVariables; } public static Document to(DocumentRouteHeaderValue documentBo) { if (documentBo == null) { return null; } Document.Builder builder = Document.Builder.create(documentBo); return builder.build(); } public static DocumentRouteHeaderValue from(Document document) { DocumentRouteHeaderValue documentBo = new DocumentRouteHeaderValue(); documentBo.setAppDocId(document.getApplicationDocumentId()); if (document.getDateApproved() != null) { documentBo.setApprovedDate(new Timestamp(document.getDateApproved().getMillis())); } if (document.getDateCreated() != null) { documentBo.setCreateDate(new Timestamp(document.getDateCreated().getMillis())); } if (StringUtils.isEmpty(documentBo.getDocContent())) { documentBo.setDocContent(KewApiConstants.DEFAULT_DOCUMENT_CONTENT); } documentBo.setDocRouteStatus(document.getStatus().getCode()); documentBo.setDocTitle(document.getTitle()); if (document.getDocumentTypeName() != null) { DocumentType documentType = KEWServiceLocator.getDocumentTypeService() .findByName(document.getDocumentTypeName()); if (documentType == null) { throw new RiceRuntimeException( "Could not locate the given document type name: " + document.getDocumentTypeName()); } documentBo.setDocumentTypeId(documentType.getDocumentTypeId()); } if (document.getDateFinalized() != null) { documentBo.setFinalizedDate(new Timestamp(document.getDateFinalized().getMillis())); } documentBo.setInitiatorWorkflowId(document.getInitiatorPrincipalId()); documentBo.setRoutedByUserWorkflowId(document.getRoutedByPrincipalId()); documentBo.setDocumentId(document.getDocumentId()); if (document.getDateLastModified() != null) { documentBo.setDateModified(new Timestamp(document.getDateLastModified().getMillis())); } documentBo.setAppDocStatus(document.getApplicationDocumentStatus()); if (document.getApplicationDocumentStatusDate() != null) { documentBo.setAppDocStatusDate(new Timestamp(document.getApplicationDocumentStatusDate().getMillis())); } // Convert the variables Map<String, String> variables = document.getVariables(); if (variables != null && !variables.isEmpty()) { for (Map.Entry<String, String> kvp : variables.entrySet()) { documentBo.setVariable(kvp.getKey(), kvp.getValue()); } } return documentBo; } @Override public void refresh() { } public DocumentRouteHeaderValue deepCopy(Map<Object, Object> visited) { if (visited.containsKey(this)) { return (DocumentRouteHeaderValue) visited.get(this); } DocumentRouteHeaderValue copy = new DocumentRouteHeaderValue(); visited.put(this, copy); copy.documentId = documentId; copy.documentTypeId = documentTypeId; copy.docRouteStatus = docRouteStatus; copy.docRouteLevel = docRouteLevel; copy.dateModified = copyTimestamp(dateModified); copy.createDate = copyTimestamp(createDate); copy.approvedDate = copyTimestamp(approvedDate); copy.finalizedDate = copyTimestamp(finalizedDate); copy.docTitle = docTitle; copy.appDocId = appDocId; copy.docVersion = docVersion; copy.initiatorWorkflowId = initiatorWorkflowId; copy.routedByUserWorkflowId = routedByUserWorkflowId; copy.routeStatusDate = copyTimestamp(routeStatusDate); copy.appDocStatus = appDocStatus; copy.appDocStatusDate = copyTimestamp(appDocStatusDate); if (documentContent != null) { copy.documentContent = documentContent.deepCopy(visited); } copy.routingReport = routingReport; if (initialRouteNodeInstances != null) { List<RouteNodeInstance> copies = new ArrayList<RouteNodeInstance>(); for (RouteNodeInstance routeNodeInstance : initialRouteNodeInstances) { copies.add(routeNodeInstance.deepCopy(visited)); } copy.setInitialRouteNodeInstances(copies); } if (appDocStatusHistory != null) { List<DocumentStatusTransition> copies = new ArrayList<DocumentStatusTransition>(); for (DocumentStatusTransition documentStatusTransition : appDocStatusHistory) { copies.add(documentStatusTransition.deepCopy(visited)); } copy.setAppDocStatusHistory(copies); } if (notes != null) { List<Note> copies = new ArrayList<Note>(); for (Note note : notes) { copies.add(note.deepCopy(visited)); } copy.setNotes(copies); } return copy; } private Timestamp copyTimestamp(Timestamp timestamp) { if (timestamp == null) { return null; } return new Timestamp(timestamp.getTime()); } }