AnsweredAssumed Answered

'jBPM-like' state transitions

Question asked by bwestrich on Nov 7, 2011
Latest reply on Feb 8, 2012 by hamed
Hello Activiti community,

I'm trying to implement a "wait-state" model in Activiti, which I believe is done via the RuntimeService.signal() method and "receive" tasks. This approach chooses transitions based on the transition condition. Though this is standard BPMN2.0 behavior, I see a couple drawbacks compared to how jBPM 3.x works:

More work to implement models (at least the way I've figured out, perhaps there's an easier way): One needs to create a transition condition on each transition such as ${transition == "t6"}, then set a process instance variable related to the condition (engine.getRuntimeService().setVariable(processInstanceId, "transition", "t6");). With jBPM, one only needs to give the transition a name.

Less transparent: At least for the short term, the Activiti Eclipse based model designer doesn't display transition conditions on the model diagram.  So the diagram doesn't show how the model will execute.

So I wrote a little code that adds the capability to choose a transition based on the transition name. Basically I wrote a new Command class based on SignalCmd named SignalViaNameCmd, then implemented a signal() method that uses this new Command:

This seems to work well, I'm interested in your comments….

Brian

P.S.  The code follows:

SignalViaNameCmd.java:

package my.package.name;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.apache.commons.lang.StringUtils;

// this class based on SignalCmd.java
public class SignalViaNameCmd implements Command<Object>, Serializable {

    public static final String INVALID_TRANSITION_MSG = " is not a valid transition for state ";
    public static final String DUPLICATE_TRANSITIONS_MSG =
        " is used as a name for more than one outgoing transition of state ";
    public static final String TOO_MANY_TRANSITIONS_MSG =
        "Can only signal without a transition name if there is exactly one outgoing transition. ";
    public static final String IF_NAMED_ALL_MUST_HAVE_NAME_MSG =
        "When signaling with a transition name, all outgoing transitions must have a name. ";

    private static final long serialVersionUID = 1L;
    protected String executionId;
    protected String signalName;

    public SignalViaNameCmd(String executionId, String signalName, Object signalData) {
        this.executionId = executionId;
        this.signalName = signalName;
    }

    public Object execute(CommandContext commandContext) {
        if (executionId == null) {
            throw new ActivitiException("executionId is null");
        }

        ExecutionEntity execution = commandContext.getExecutionManager().findExecutionById(executionId);

        if (execution == null) {
            throw new ActivitiException("execution " + executionId + " doesn't exist");
        }

        signal(execution);
        return null;
    }

    // This method is loosely based on BpmnActivityBehavior.java.
    private void signal(ExecutionEntity execution) {
        // next line is inspired by ExecutionEntity.signal
        ActivityImpl activity = execution.getActivity();
        List<PvmTransition> transitionsToTake = new ArrayList<PvmTransition>();
        List<PvmTransition> outgoingTransitions = activity.getOutgoingTransitions();
        if (signalName == null) {
            if (outgoingTransitions.size() != 1) {
                throw new ActivitiException(TOO_MANY_TRANSITIONS_MSG + " State = '"
                    + activity.getProperty("name") + "'");
            } else {
                execution.take(outgoingTransitions.get(0));
                return;
            }
        }
        for (PvmTransition outgoingTransition : outgoingTransitions) {
            String name = (String) outgoingTransition.getProperty("name");
            if (StringUtils.isBlank(name)) {
                throw new ActivitiException(IF_NAMED_ALL_MUST_HAVE_NAME_MSG
                    + showStateNameId(signalName, activity, outgoingTransition));
            }
            if (signalName.equals(name)) {
                transitionsToTake.add(outgoingTransition);
            }
        }
        if (transitionsToTake.size() == 0) {
            throw new ActivitiException("'" + signalName + "'" + INVALID_TRANSITION_MSG + "'"
                + activity.getProperty("name") + "'");
        }
        if (transitionsToTake.size() > 1) { // bpmn supports this, but for
            // simplicity we do not yet
            throw new ActivitiException("'" + signalName + "'" + DUPLICATE_TRANSITIONS_MSG + "'"
                + activity.getProperty("name") + "'");
        }
        execution.take(transitionsToTake.get(0));
    }

    private String showStateNameId(String transitionName, ActivityImpl activity,
            PvmTransition outgoingTransition) {
        String message =
            " State = '" + activity.getProperty("name") + "', signaled transition = '" + transitionName
                + "', transition Id = '" + outgoingTransition.getId() + "'";
        return message;
    }

}

signal() method that uses the above command …..


    public void signal(ProcessEngine engine, String processInstanceId, String transitionName) {
        CommandExecutor commandExecutor =
            ((RuntimeServiceImpl) engine.getRuntimeService()).getCommandExecutor();
        SignalViaNameCmd command = new SignalViaNameCmd(processInstanceId, transitionName, null);
        commandExecutor.execute(command);
    }

Brian

Brian Westrich
bw@mcwest.com

Outcomes