Friday, April 9, 2010

Automatic continuations in a jBPM Node

The normal pattern of using a Node would be to execute some Java code from within an Action directly attached to the Node, but as the documentation states, that means this code will also be responsible for continuing the process execution:

"The nodetype node expects one subelement action. The action is executed when the execution arrives in the node. The code you write in the actionhandler can do anything you want but it is also responsible for propagating the execution."

And you may have a different opinion, but I think it's quite tedious to have to repeat the same kind of boiler plate code in each and every ActionHandler implementation used in Nodes, so I wanted to come up with a way to do it more generically.

This standard node type actually gives you a choice as it comes to its execution:
  • as stated above you add an Action (directly to the Node) and have it execute that, or
  • you don't add an Action and have it leave through the default Transition.
How the latter would be useful is beyond me (but that's another discussion), you should be aware of this behavior when you're e.g. attaching Actions to the 'node-enter' event only and not directly to the Node. You'd have a hard time figuring out what happens if you would expect it would be possible to leave the Node through another than the default Transition (it's possible, but you'd have to change the order of the Transitions on runtime, which you probably want to stay away from as far as possible).

The simple approach I chose to illustrate this was to provide an abstract base class, which implements the ActionHandler interface, which has to be extended by all action handlers in your code base. Well, nearly all, but I'll get back to that later. Surely there would be other approaches that will come up with the same result (like annotations or aspects); just knock yourself out.
Such a base class would look something like this:

public abstract class AbstractActionHandler implements ActionHandler {
    protected String transitionName;

    public final void execute(ExecutionContext ctx) throws Exception {
        performAction(ctx);

        if (ctx.getEvent() == null) {
            // When leaving the node we can either have a transition set to be taken or else take the default transition.
            if (StringUtils.isBlank(transitionName)) {
                ctx.getNode().leave(ctx);
            } else {
                ctx.getNode().leave(ctx, transitionName);
            }
        }
    }

    // To be implemented by concrete subclasses; execute the intended Java code and optionally set the transition to be taken.
    public abstract void performAction(ExecutionContext ctx) throws Exception;
}

Now the main 'trick' here is to know when to continue the execution and when not to; as you can tell from the code above this can be derived from the fact whether an event is available in the execution context. At the basis of this fact is the knowledge at which places in a process definition Actions can be added (and when/how they're executed in those instances), and cross-reference that with the required point of continuation (an Action directly in a Node).

You can add an Action at six different places:
  • Directly to a Node: which is what we're talking about here for having automatic continuation.
  • In an event (e.g. 'node-enter'): most of the time that's an explicit event in the process definition.
  • In a Transition: actually then it's executed from within a 'transition' event.
  • In a timer: here it's executed after the 'timer' event is fired, so not within it.
  • In an exception handler: executed from within GraphElement's raiseException(...) method, which also has no event associated with it, but does put the current Exception in the execution context.
  • Directly to the process definition (highest level): these are just for reference from within other elements, so not an 'extra' type in any sense - so we'll just forget about this one for now.
So for the second and third entries of this list the execution context has a current event; the first, fourth and the fifth don't. So the 'trick' as it is used in the above code fragment works for the first three, for the other two (timers and exception handlers) you shouldn't use that particular base class.
It is however possible to extend the 'trick' for these other two instances, by checking the execution context for the availability of a timer (ctx.getTimer() == null) and/or the availability of an exception (ctx.getException() == null) respectively - it depends for which of the cases you want to provide a base class (or mechanism of your choice) in order to have these automatic continuations I was after.

Credit where credit is due: thanks to Arnoud W. for the hint!