4 Replies Latest reply on Aug 9, 2007 11:32 PM by Igor Klinchin

    Where to route obsolete parent token in token multiplication

    Igor Klinchin Newbie

      Hi All,

      Based on the examples from this forum and jBPM wiki I have implemented a custom action that accepts a list of items, generates a token for each item from the list, creates a variable with the list item for each token and sends the token further to the process. This action could be used in any node.

      It all works perfectly but there is an original parent token that came to this node (with the list of items) that I do not need anymore. However, I would need all variables associated with this token because they are inherited by the children tokens generated by the action.

      I could reuse this parent token as one of the child tokens but this is bad idea because of two reasons: a) there could be no items in the list so the question would still remain: where to route the parent token since no tokens should go along the process anymore; b) this new reused parent token would carry all the variables inherited by all other child tokens which is not right - some variables should be private for each token.

      I could also create a decision node later in the process flow that would route the parent token to the end of the process based on some business value - this parent token will definitely lack some important variable, f.i. the one which keeps the item from the list - all siblings would have it but the parent would not. This solution would complicate the process topology unnecessary.

      I could not end this parent token because it will effectively terminate all children with all the variables. I could not also suspend this token because I would not know when to restart it and (eventually) where to route it after un-suspend.

      So the question is: is there any way to route this parent token in the action code right to the end of the process or to some designated node (join?) where it would wait for all of its children without having the process to know about it?

      Thank you,
      Mark

        • 1. Re: Where to route obsolete parent token in token multiplica
          Edward Staub Expert

          All child tokens, when they're done, should/could transition to a join. The join will count down the outstanding tokens for the parent token, and allow it to resume when the last child completes.

          To avoid other potential problems, I suggest that you make your token-generating node a fork.

          I believe that there's a race condition leading to Hibernate StaleObject exceptions in join. Before you go into production, be sure to do some concurrency testing on join.

          -Ed Staub

          • 2. Re: Where to route obsolete parent token in token multiplica
            Igor Klinchin Newbie

            Hi Ed,

            Thank you for the reply. I made this token-generating node a fork. One of the transition is the transition with the specific name "root". In the node action I route the root token (the one that came into the node with the list variable) to this "root" transition. The other, generated, tokens leave to all other transitions except the "root" one.

            It all works perfectly. I can now even route the root token to any node in the workflow right in the flow definition. It seems like exactly what I needed.

            Thank you,
            Mark

            • 3. Re: Where to route obsolete parent token in token multiplica
              Jamie Cruise Newbie

              Hi Mark,

              I am trying to solve a similar problem, but am a bit of a custom node newbie ... do you have any code that you can share for your custom fork node?

              Optimistically,
              J

              • 4. Re: Where to route obsolete parent token in token multiplica
                Igor Klinchin Newbie

                Hi J!

                Here is the code for the custom fork. The code is a little bit too tailored to our specific needs and we are still thinking how to do it better. I hope it helps. You comments are appreciated.

                Thank you,
                Mark

                import java.util.LinkedList;
                import java.util.List;
                
                import org.jbpm.graph.def.ActionHandler;
                import org.jbpm.graph.def.Node;
                import org.jbpm.graph.def.Transition;
                import org.jbpm.graph.exe.ExecutionContext;
                import org.jbpm.graph.exe.Token;
                
                public class MultiplexorAction implements ActionHandler {
                
                 private static final long serialVersionUID = -5530634487359948748L;
                
                 //
                 // Name of the process variable that contains the list of items
                 //
                 private String listVariable;
                
                 //
                 // Name of the process variable that will contain the item -
                 // output for each new token
                 //
                 protected String as;
                
                 private List list;
                 protected static final String FOREACH_PREFIX = "ea";
                
                 /**
                 * Create a new child token for each item in list.
                 *
                 * @param executionContext
                 * @throws Exception
                 */
                 public void execute(final ExecutionContext executionContext)
                 throws Exception {
                 final Token rootToken = executionContext.getToken();
                 final Node node = executionContext.getNode();
                
                 initializeList(executionContext, rootToken);
                
                 final List argSets = new LinkedList();
                 Transition rootTransition = null;
                 Transition defaultTransition = null;
                
                 //
                 // First, create a new token and execution context for each item in
                 // list.
                 //
                 for (int i = 0; i < node.getLeavingTransitions().size(); i++) {
                 final Transition transition = (Transition) node
                 .getLeavingTransitions().get(i);
                
                 //
                 // A transition that starts from the words "root exit"
                 // will be reserved for the original (root) token
                 // that came to the multiplexor node
                 //
                 if (transition.getName() != null
                 && transition.getName().toLowerCase().startsWith(
                 "root exit")) {
                 rootTransition = transition;
                 continue;
                 }
                
                 //
                 // A transition that starts from the words "default exit"
                 // will be reserved for the new token
                 // that will be the exact copy of the original (root token)
                 //
                 if (transition.getName() != null
                 && transition.getName().toLowerCase().startsWith(
                 "default exit")) {
                 defaultTransition = transition;
                 continue;
                 }
                
                 for (int j = 0; list != null && j < list.size(); j++) {
                 final Object item = list.get(j);
                
                 // generate a name for the new child token
                 String tokenName = FOREACH_PREFIX + "." + node.getId() + "."
                 + j;
                
                 // generate and save new token
                 final Token newToken = new Token(rootToken, tokenName);
                 newToken.setTerminationImplicit(true);
                 executionContext.getJbpmContext().getSession().save(newToken);
                
                 // create token variable from the list item
                 final ExecutionContext newExecutionContext = new ExecutionContext(
                 newToken);
                 newExecutionContext.getContextInstance().createVariable(as,
                 item, newToken);
                
                 // remember this new token with its context so we can
                 // launch it later
                 argSets.add(new Object[] { newExecutionContext, transition });
                 }
                 }
                
                 // Delete the original list variable.
                 // This step could be done in the process itself
                 // since someone may still want reuse this list
                 executionContext.getContextInstance().deleteVariable(listVariable);
                
                 //
                 // Now, let each new tokens leave the node.
                 //
                 for (int i = 0; i < argSets.size(); i++) {
                 final Object[] args = (Object[]) argSets.get(i);
                 node.leave((ExecutionContext) args[0], (Transition) args[1]);
                 }
                
                 //
                 // Generate the token for the default node and launch it
                 //
                 if (defaultTransition != null) {
                 String tokenName = "df." + node.getId();
                
                 final Token newToken = new Token(rootToken, tokenName);
                 newToken.setTerminationImplicit(true);
                 executionContext.getJbpmContext().getSession().save(newToken);
                 final ExecutionContext newExecutionContext = new ExecutionContext(
                 newToken);
                 node.leave(newExecutionContext, defaultTransition);
                 }
                
                 //
                 // Send the original root token that came to this node with the list
                 // to the root exit transition. The assumed case is that this
                 // original token would come to the join node to wait for all its
                 // children generated above.
                 //
                 // If the original root token is routed to some business nodes
                 // then after all its children would come
                 // to the join node, it will effectively terminate this original
                 // token. This behavior would leave the only option for this
                 // root token: to be routed to the join node to wait for all
                 // its children to complete.
                 //
                 if (rootTransition != null) {
                 node.leave(executionContext, rootTransition);
                 }
                 }
                
                 public List getList() {
                 return list;
                 }
                
                 public void setList(final List list) {
                 this.list = list;
                 }
                
                 public String getAs() {
                 return as;
                 }
                
                 public void setAs(final String as) {
                 this.as = as;
                 }
                
                 public String getListVariable() {
                 return listVariable;
                 }
                
                 public void setListVariable(final String listVariable) {
                 this.listVariable = listVariable;
                 }
                
                 protected void initializeList(final ExecutionContext executionContext,
                 Token token) {
                 if (listVariable != null) {
                 list = (List) executionContext.getContextInstance().getVariable(
                 listVariable, token);
                 if (list == null)
                 list = (List) executionContext.getContextInstance()
                 .getVariable(listVariable);
                 }
                 }
                }