SCXML Workflow Actions and Tasks
Overview
The Bloomreach Content SCXML Workflow Engine enables you to define and extend custom SCXML actions. These actions are based on the Apache Commons SCXML org.apache.commons.scxml2.model.Action class.
Custom actions allow you to create SCXML elements that execute arbitrary code within the context of the current SCXML state. You must configure these custom actions under a specific namespace along with the SCXML Workflow Definition.
Important:
Custom actions cannot use instance data. The SCXML engine creates only one instance per configured element in the state machine, and multiple SCXML state machine instances may invoke the same action concurrently.
Custom actions can use expression evaluation in their element attributes. However, these expressions are evaluated only at runtime, not at load time.
To support these requirements, the Hippo SCXML Workflow Engine provides the org.onehippo.repository.scxml.AbstractAction class. Use this class as the base for all custom SCXML Workflow actions, rather than the Apache Commons SCXML Action class.
Using and Extending the AbstractAction Class
The org.onehippo.repository.scxml.AbstractAction class extends Apache Commons SCXML Action and adds convenience methods and thread-safety guards. It provides access to the current SCXMLWorkflowContext, SCXMLWorkflowData, and the Apache Commons SCXML Context through ThreadLocal variables.
To define custom element attributes, implement bean-style String property setter methods in your custom action. The SCXML engine invokes these setters once per element definition during the initial load of the SCXML document.
Note:
Because setters are called only once at load time, the provided String values must be immutable and remain valid for the lifetime of the SCXML state machine.
Use the protected Map<String, String> getParameters() method in AbstractAction to store attribute values. Do not use instance variables for this purpose. The parameters map becomes immutable the first time the action is executed.
If an attribute value is an expression that should be evaluated at runtime, use the protected <T> T AbstractAction.eval(String expr) method inside the doExecute() method.
Example: Custom Action Implementation
The following example shows a custom action, org.onehippo.repository.scxml.ActionAction. This action registers the enabled or disabled state for an action (event) in the SCXMLWorkflowContext.
public class ActionAction extends AbstractAction { private static final long serialVersionUID = 1L; public String getAction() { return getParameter("action"); } public void setAction(final String action) { setParameter("action", action); } public String getEnabledExpr() { return getParameter("enabledExpr"); } public void setEnabledExpr(final String enabled) { setParameter("enabledExpr", enabled); } @Override protected void doExecute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException { String action = getAction(); if (StringUtils.isBlank(action)) { throw new ModelException("No action specified"); } String enabledExpr = getEnabledExpr(); Boolean enabled = (StringUtils.isBlank(enabledExpr) ? null : (Boolean)eval(enabledExpr)); if (enabled == null) { getSCXMLWorkflowContext().getActions().remove(action); } else { getSCXMLWorkflowContext().getActions().put(action, enabled); } } }
You can use this action in SCXML as follows:
<hippo:action action="checkModified" enabledExpr="draft and unpublished"/>
In this example, the enabledExpr attribute value "draft and unpublished" is stored in the parameters map and evaluated only when the action executes.
In addition to ActionAction, the Hippo SCXML Workflow Engine provides generic actions such as FeedbackAction, ResultAction, and WorkflowExceptionAction. For details, see SCXML Workflow Execution.
Using and Extending the AbstractWorkflowTaskAction Class
If your custom action needs to perform workflow-specific operations, extend org.onehippo.repository.scxml.AbstractWorkflowTaskAction instead of AbstractAction.
AbstractWorkflowTaskAction delegates workflow operations to a separate implementation of the org.onehippo.repository.api.WorkflowTask interface. This approach allows you to implement operations that are reusable outside the SCXML Workflow Engine. The WorkflowTask interface defines a single method:
Object execute() throws WorkflowException;
Future versions of the Hippo Repository may introduce a task execution engine that can reuse these WorkflowTask implementations independently of SCXML.
The AbstractWorkflowTaskAction<T extends WorkflowTask> class manages the instantiation, initialization, invocation, and result processing for each execution of a workflow task.
Example: DocumentWorkflow Task Action
The following example shows the SetHolderAction class, which extends the DocumentWorkflow-specific AbstractDocumentTaskAction. This action sets the holder of a document.
public class SetHolderAction extends AbstractDocumentTaskAction<SetHolderTask> { private static final long serialVersionUID = 1L; public void setHolder(String holder) { setParameter("holderExpr", holder); } public String getHolder() { return getParameter("holderExpr"); } @Override protected SetHolderTask createWorkflowTask() { return new SetHolderTask(); } @Override protected void initTask(SetHolderTask task) throws ModelException, SCXMLExpressionException { super.initTask(task); String holder = getHolder(); if (holder != null) { task.setHolder((String) eval(getHolder())); } } }
The related SetHolderTask class is implemented as follows:
public class SetHolderTask extends AbstractDocumentTask { private String holder; public String getHolder() { return holder; } public void setHolder(final String holder) { this.holder = holder; } @Override public Object doExecute() throws WorkflowException, RepositoryException { DocumentHandle dm = getDocumentHandle(); DocumentVariant draft = dm.getDocuments().get(HippoStdNodeType.DRAFT); if (draft != null) { draft.setHolder(holder); } else { throw new WorkflowException("Draft document not available"); } return null; } }
Key Point:
The SetHolderTask class does not depend on the SCXML Workflow Engine. This separation allows you to reuse workflow tasks outside the SCXML context.
The org.onehippo.repository.documentworkflow.task.AbstractDocumentTask class (not shown) provides utility methods for working with Hippo Documents but also does not depend on the SCXML Workflow Engine.