SCXML Workflow Actions and Tasks - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 11. There's an updated version available that covers our most recent release.

04-07-2016

SCXML Workflow Actions and Tasks

SCXML Workflow Actions and Tasks

The Hippo SCXML Workflow Engine provides a set of base classes to define and extend custom SCXML actions, based on the Apache Commons SCXML org.apache.commons.scxml2.model.Action class.

Such custom actions define custom SCXML elements which can be used within a SCXML state machine to execute arbitrary code within the context of the current SCXML state.

These custom actions must be configured under a specific namespace together with the SCXML Workflow Definition.

Very important to note is that such custom actions cannot use any instance data as only one instance will be created per configured element within the SCXML state machine and that they can be invoked concurrently through multiple SCXML state machine instances.

This also means that, while such custom actions can leverage expression evaluation in their custom element attributes, these expressions can and may only be evaluated during their runtime execution, not at load time!

For this purpose the Hippo SCXML Workflow Engine provides an org.onehippo.repository.scxml.AbstractAction class which should be used as base class for all custom SCXML Workflow actions instead of the Apache Commons SCXML Action class.

Using and extending the AbstractAction class

The org.onehippo.repository.scxml.AbstractAction class extends Apache Commons SCXML org.apache.commons.scxml2.model.Action and provide extra convenience methods (and guarding) to make it easier to implement custom Actions which are thread save and also provide access to the current SCXMLWorkflowContext, SCXMLWorkflowData and the current Apache Commons SCXML Context as well, using ThreadLocal variables managed internally.

A custom action can define custom element attributes by defining bean String property setter methods, which at runtime then will be invoked by the SCXML engine during the initial load of the SCXML document.

As these bean property setter methods are only invoked once, per element definition, and only during loading of the SCXML document, the provided (String) values must be immutable and retained across SCXML state machine executions!

For this purpose, the AbstractAction provides a protected Map<String, String> getParameters() method which must be used to store the provided value on the custom attribute setter method. Instance variables should not be used for this.

This internal parameters map will automatically be 'locked down' and made immutable the first time the action itself is invoked through the SCXML state machine.

If the custom attribute value represents an expression, to be evaluated at runtime during the execution of the action, the protected <T> T AbstractAction.eval(String expr) method can be used from within its doExecute() method.

A good and relatively simple example of a custom action is the org.onehippo.repository.scxml.ActionAction which can be used to registere 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);
        }
    }
}

which then can be used like:

<hippo:action action="checkModified" enabledExpr="draft and unpublished"/>

In the above example, the enabledExpr attribute with value "draft and unpublished" is literally stored in the internal parameters map, and only dynamically evaluated during execution of the action.

Besides the above shown ActionAction class, the Hippo SCXML Workflow Engine also provides the generic FeedbackAction, ResultAction and WorkflowExceptionAction classes, which usage is explained in detail in SCXML Workflow Execution.

Using and extending the AbstractWorkflowTaskAction class

While only extending the AbstractAction class as described above is fine for custom actions which operate only in the context of the SCXML state machine itself, for workflow specific operations the org.onehippo.repository.scxml.AbstractWorkflowTaskAction should be used and extended instead.

The purpose of the AbstractWorkflowTaskAction is to delegate these workflow specific operations to a separate org.onehippo.repository.api.WorkflowTask interface implementation, to ensure these operations are implemented as well as independently usable outside the context of the SCXML Workflow Engine. The WorkflowTask interface itself only defines one simple Object execute() throws WorkflowException; method to be implemented.

In the future the Hippo Repository may introduce an additional Task Execution engine, which then can reuse and combine such WorkflowTask implementations without need for providing a SCXML context.

For this purpose the AbstractWorkflowTaskAction<T extends WorkflowTask> extends AbstractAction class is provided which handles (for each execution) WorkflowTask instantiation, initialization, invocation and processing a returned result.

A relatively simple example is the DocumentWorkflow specific SetHolderAction, which furthermore extends the DocumentWorkflow specific AbstractDocumentTaskAction although that is not relevant here:

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()));
        }
    }
}

And the related SetHolderTask is:

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;
    }
}

The important point from this example is that the SetHolderTask class has no, and should not have any, dependencies on the SCXML Workflow Engine.

The not shown here org.onehippo.repository.documentworkflow.task.AbstractDocumentTask does provide additional generic utility methods specific for the handling and operation on Hippo Documents, but also has no dependencies on the SCXML Workflow Engine.

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?