/* * Copyright 2002-2005 the original author or authors. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 * * 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.springmodules.workflow.osworkflow; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.StringUtils; import com.opensymphony.module.propertyset.PropertySet; import com.opensymphony.workflow.Workflow; import com.opensymphony.workflow.WorkflowException; import com.opensymphony.workflow.basic.BasicWorkflow; import com.opensymphony.workflow.config.Configuration; import com.opensymphony.workflow.config.DefaultConfiguration; import com.opensymphony.workflow.loader.WorkflowDescriptor; import com.opensymphony.workflow.query.WorkflowExpressionQuery; import com.opensymphony.workflow.spi.Step; /** * Template class that simplifies interaction with the <a href="http://www.opensymphony.com/osworkflow">OSWorkflow</a> * workflow engine by hiding the management of context parameters such as the caller and workflow ID. * <p/> * Translates all checked <code>com.opensymphony.workflow.WorkflowException</code>s into unchecked * <code>org.springmodules.workflow.WorkflowException</code>s. * <p/> * Most operations on the <code>Workflow</code> instance are mirrored here, but for operations that require * lengthy interaction with a <code>Workflow</code> a custom <code>OsWorkflowCallback</code> can be used in conjunction * with the <code>execute(OsWorkflowCallback)</code> method. * <p/> * It is intended that a single <code>OsWorkflowTemplate</code> will be used to manage all instances of a single workflow * within your application. Because of this, the workflow name is not a parameter on any of operations exposed by this * class. Instead, the workflow name is set via the required <code>workflowName</code> property. The same workflow name * is used for all operations. * <p/> * Workflow context parameters such as the caller name and current instance ID are stored in an <code>OsWorkflowContext</code> * which is bound to the current thread using the <code>OsWorkflowContextHolder</code>. It is intended that context * information be set externally to the core application code. For this purpose, Spring Modules provides the * <code>AbstractWorkflowContextHandlerInterceptor</code> (and its implementations) which allows for context information * to be managed transparently in a web environment. * <p/> * Both the <code>workflowName</code> and <code>contextManager</code> parameters are required. * <p/> * * @author Rob Harrop * @see WorkflowException * @see org.springmodules.workflow.WorkflowException * @see #execute(OsWorkflowCallback) * @see OsWorkflowContext * @see OsWorkflowContextHolder * @see org.springmodules.workflow.osworkflow.web.AbstractWorkflowContextHandlerInterceptor * @see #setWorkflowName(String) * @since 0.2 */ public class OsWorkflowTemplate implements InitializingBean { /** * The <code>Configuration</code> used to load workflow definitions. Uses the OSWorkflow <code>DefaultConfiguration</code> * class by default. */ private Configuration configuration = new DefaultConfiguration(); /** * The ID of the initial action to call when initializing a workflow instance. Defaults to <code>0</code>. */ private Integer initialAction = new Integer(0); /** * The name of the workflow definition to use. */ private String workflowName; /** * Sets the <code>Configuration<code> used to load workflow definitions. */ public void setConfiguration(Configuration configuration) { this.configuration = configuration; } /** * Sets the inital action used when initializing a workflow instance. */ public void setInitialAction(Integer initialAction) { this.initialAction = initialAction; } /** * Sets the name of the workflow definition to use. Required. */ public void setWorkflowName(String workflowName) { this.workflowName = workflowName; } /** * Gets the workflow name. */ public String getWorkflowName() { return this.workflowName; } /** * Checks that both the <code>workflowName</code> and <code>contextManager</code> properties have both been specified. * * @throws FatalBeanException if any of the required properties is not set. */ public void afterPropertiesSet() throws Exception { if (!StringUtils.hasText(this.workflowName)) { throw new FatalBeanException("Property [workflowName] is required."); } } /** * Initialize a workflow instance using the default initial action. * * @see #setInitialAction(Integer) * @see #initialize(int, java.util.Map) */ public void initialize() { this.initialize(this.initialAction.intValue(), null); } /** * Initialize a workflow instance using the default initial action and the supplied inputs. * * @see #setInitialAction(Integer) * @see #initialize(int, java.util.Map) */ public void initialize(final Map inputs) { this.initialize(this.initialAction.intValue(), inputs); } /** * Initialize a workflow instance using the supplied inital action. * * @see #initialize(int, java.util.Map) */ public void initialize(final int initialAction) { this.initialize(initialAction, null); } /** * Initialize a workflow instance using the supplied initial action and inputs. The caller's identity is retreived * from the <code>WorkflowContextManager</code>. The resulting workflow instance ID is stored using the * <code>WorkflowContextManager</code>. * * @see OsWorkflowContext#getCaller() * @see OsWorkflowContext#setInstanceId(long) */ public void initialize(final int initialAction, final Map inputs) { this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { long id = workflow.initialize(OsWorkflowTemplate.this.workflowName, initialAction, inputs); bindInstanceIdToWorkflowContext(id); return null; } }); } /** * Do the workflow action specified by the given action ID. * * @see #doAction(int, java.util.Map) */ public void doAction(final int actionId) { this.doAction(actionId, null); } /** * Do the workflow action specified by the given action ID. Passes in a single input under the specified key. * * @param actionId the ID of the action to execute * @param inputKey the key of the input * @param inputVal the value of the input * @see #doAction(int, java.util.Map) */ public void doAction(final int actionId, Object inputKey, Object inputVal) { Map inputs = new HashMap(); inputs.put(inputKey, inputVal); this.doAction(actionId, inputs); } /** * Do the workflow action specified by the given action ID passing in the supplied inputs. The workflow instance ID * to execute the action against is retreived from the <code>WorkflowContextManager</code>. * * @see OsWorkflowContext#getInstanceId() */ public void doAction(final int actionId, final Map inputs) { this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { workflow.doAction(getInstanceId(), actionId, inputs); return null; } }); } /** * Gets the <code>WorkflowDescriptor</code> for the configured workflow. * * @see #getWorkflowName() * @see #setWorkflowName(String) */ public WorkflowDescriptor getWorkflowDescriptor() { return (WorkflowDescriptor) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.getWorkflowDescriptor(OsWorkflowTemplate.this.workflowName); } }); } /** * Gets the <code>List</code> of history <code>Step</code>s for the current workflow instance. */ public List getHistorySteps() { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.getHistorySteps(getInstanceId()); } }); } /** * Gets the <code>List</code> of current <code>Step</code>s for the current workflow instance. */ public List getCurrentSteps() { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.getCurrentSteps(getInstanceId()); } }); } /** * Gets the <code>List</code> of history <code>StepDescriptor</code>s for the current workflow instance. */ public List getHistoryStepDescriptors() { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { List steps = workflow.getHistorySteps(getInstanceId()); return convertStepsToStepDescriptors(steps, workflow); } }); } /** * Gets the <code>List</code> of current <code>StepDescriptor</code>s for the current workflow instance. */ public List getCurrentStepDescriptors() { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { List steps = workflow.getCurrentSteps(getInstanceId()); return convertStepsToStepDescriptors(steps, workflow); } }); } /** * Gets the IDs of actions available to the caller on the current workflow instance. */ public int[] getAvailableActions() { return this.getAvailableActions(null); } /** * Gets the IDs of actions available to the caller that match the supplied inputs on the current workflow instance. */ public int[] getAvailableActions(final Map inputs) { return (int[]) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.getAvailableActions(getInstanceId(), inputs); } }); } /** * Gets a <code>List</code> of the <code>ActionDescriptor</code>s available to the caller on the current workflow instance. */ public List getAvailableActionDescriptors() { return this.getAvailableActionDescriptors(null); } /** * Gets a <code>List</code> of the <code>ActionDescriptor</code>s available to the caller that match the supplied * inputs on the current workflow instance. */ public List getAvailableActionDescriptors(final Map inputs) { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { WorkflowDescriptor descriptor = workflow.getWorkflowDescriptor(OsWorkflowTemplate.this.workflowName); int[] availableActions = workflow.getAvailableActions(getInstanceId(), inputs); List actionDescriptors = new ArrayList(availableActions.length); for (int i = 0; i < availableActions.length; i++) { actionDescriptors.add(descriptor.getAction(availableActions[i])); } return actionDescriptors; } }); } /** * Gets the entry state for the current workflow instance. */ public int getEntryState() { Integer state = (Integer) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return new Integer(workflow.getEntryState(getInstanceId())); } }); return state.intValue(); } /** * Gets the <code>PropertySet</code> for the current workflow instance. * * @return */ public PropertySet getPropertySet() { return (PropertySet) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.getPropertySet(getInstanceId()); } }); } /** * Returns <code>true</code> if the configured workflow can be configured with the supplied initial step. * * @see #setWorkflowName(String) * @see #getWorkflowName() */ public boolean canInitialize(final int initialStep) { return this.canInitialize(initialStep, null); } /** * Returns <code>true</code> if the configured workflow can be configured with the supplied initial step and inputs. * * @see #setWorkflowName(String) * @see #getWorkflowName() */ public boolean canInitialize(final int initialStep, final Map inputs) { Boolean returnValue = (Boolean) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { boolean canInit = workflow.canInitialize(OsWorkflowTemplate.this.workflowName, initialStep, inputs); return (canInit) ? Boolean.TRUE : Boolean.FALSE; } }); return returnValue.booleanValue(); } /** * Returns <code>true</code> if the entry state of the current workflow can be modified to the supplied state. * * @see #changeEntryState(int) */ public boolean canModifyEntryState(final int newState) { Boolean returnValue = (Boolean) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { boolean canModify = workflow.canModifyEntryState(getInstanceId(), newState); return (canModify) ? Boolean.TRUE : Boolean.FALSE; } }); return returnValue.booleanValue(); } /** * Changes the entry state of the current workflow to the supplied state. * * @see #canModifyEntryState(int) */ public void changeEntryState(final int newState) { this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { workflow.changeEntryState(getInstanceId(), newState); return null; } }); } /** * Executes the supplied <code>WorkflowExpressionQuery</code> and returns a <code>List</code> of IDs that match the * query criteria. * * @see com.opensymphony.workflow.query.WorkflowExpressionQuery * @see com.opensymphony.workflow.Workflow#query(com.opensymphony.workflow.query.WorkflowExpressionQuery) */ public List query(final WorkflowExpressionQuery query) { return (List) this.execute(new OsWorkflowCallback() { public Object doWithWorkflow(Workflow workflow) throws WorkflowException { return workflow.query(query); } }); } /** * Executes the supplied <code>OsWorkflowCallback</code> against the current workflow instance as the caller bound * to the current <code>OsWorkflowContext</code>. */ public Object execute(OsWorkflowCallback callback) { try { Workflow workflow = createWorkflow(OsWorkflowContextHolder.getWorkflowContext().getCaller()); workflow.setConfiguration(this.configuration); return callback.doWithWorkflow(workflow); } catch (WorkflowException ex) { // TODO: proper exception translation throw new org.springmodules.workflow.WorkflowException("", ex); } } /** * Creates a <code>Workflow</code> for the supplied caller. The method acts as a hook * for subclasses that want to change the way the workflow is created. */ protected Workflow createWorkflow(String caller) throws WorkflowException { return new BasicWorkflow(caller); } /** * Binds the supplied instance ID to the current <code>OsWorkflowContext</code>. */ protected void bindInstanceIdToWorkflowContext(long id) { OsWorkflowContextHolder.getWorkflowContext().setInstanceId(id); } /** * Retrieves the instance ID bound to the current <code>OsWorkflowContext</code>. */ protected long getInstanceId() { return OsWorkflowContextHolder.getWorkflowContext().getInstanceId(); } /** * Converts a <code>List</code> of <code>Step</code>s to a <code>List</code> of <code>StepDescriptor</code>s. */ private List convertStepsToStepDescriptors(List steps, Workflow workflow) { WorkflowDescriptor descriptor = workflow.getWorkflowDescriptor(OsWorkflowTemplate.this.workflowName); List stepDescriptors = new ArrayList(); for (int i = 0; i < steps.size(); i++) { Step step = (Step) steps.get(i); stepDescriptors.add(descriptor.getStep(step.getStepId())); } return Collections.unmodifiableList(stepDescriptors); } }