0 Replies Latest reply on Oct 18, 2006 1:06 PM by alf_dave

    Parallel Review Process: template process definition

    alf_dave

      I believe the jBPM community can greatly benefit from a library of demo/template process definitions for common workflows.

      The current code samples, docs & references to workflow patterns concentrate on the low level building blocks, but it requires a blind jump and effort to pull together the blocks into a coherent workflow.

      So, to start, I'd like to put forward the following template definition for supporting a review & approve process where 'N' parallel reviewers can approve or reject. Approval is only reached when a specified percentage of the reviewers approve.

      A custom 'ForEachFork' (modified from the contribution on the wiki) is used to implement the parallel part. The process variables 'reviewers' (a list) and 'required_approve_percent' need to be provided when starting the workflow.

      Is there a better way?

      Would it be useful to start a library of these on the WIKI?

      Regards,
      David Caruana
      Alfresco


      <process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="parallelreview">
      
       <swimlane name="initiator"></swimlane>
      
       <start-state name="start">
       <task name="submit" swimlane="initiator" />
       <transition name="" to="startreview">
       <script>
       <variable name="approve_count" access="write" />
       <expression>
       approve_count = 0;
       </expression>
       </script>
       </transition>
       </start-state>
      
       <node name="startreview">
       <action class="ForEachFork">
       <foreach>#{reviewers}</foreach>
       <var>reviewer</var>
       </action>
       <transition name="review" to="review" />
       </node>
      
       <task-node name="review">
       <task name="review">
       <event type="task-create">
       <script>
       taskInstance.actorId = reviewer;
       </script>
       </event>
       </task>
       <transition name="reject" to="endreview" />
       <transition name="approve" to="endreview">
       <script>
       <variable name="approve_count" access="read,write" />
       <expression>
       approve_count = approve_count +1;
       </expression>
       </script>
       </transition>
       </task-node>
      
       <join name="endreview">
       <transition to="isapproved" />
       </join>
      
       <decision name="isapproved">
       <event type="node-enter">
       <script>
       <variable name="approve_percent" access="write"/>
       <expression>
       approve_percent = ((approve_count * 100) / reviewers.size());
       </expression>
       </script>
       </event>
       <transition name="reject" to="rejected" />
       <transition name="approve" to="approved">
       <condition>#{approve_percent >= required_approve_percent}</condition>
       </transition>
       </decision>
      
       <task-node name="rejected">
       <task name="rejected" swimlane="initiator" />
       <transition to="end" />
       </task-node>
      
       <task-node name="approved">
       <task name="approved" swimlane="initiator" />
       <transition to="end" />
       </task-node>
      
       <end-state name="end"/>
      
      </process-definition>
      


      The ForEachFork.java:

      
      import java.util.ArrayList;
      import java.util.Collection;
      import java.util.List;
      
      import org.dom4j.Element;
      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;
      import org.jbpm.instantiation.FieldInstantiator;
      import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator;
      
      
      /**
       * For each "item in collection", create a fork.
       */
      public class ForEachFork implements ActionHandler
      {
       private static final long serialVersionUID = 4643103713602441652L;
      
       private Element foreach;
       private String var;
      
      
       /**
       * Create a new child token for each item in list.
       *
       * @param executionContext
       * @throws Exception
       */
       @SuppressWarnings("unchecked")
       public void execute(final ExecutionContext executionContext)
       throws Exception
       {
       //
       // process action handler arguments
       //
      
       if (foreach == null)
       {
       throw new Exception("forEach has not been provided");
       }
      
       // build "for each" collection
       List forEachColl = null;
       String forEachCollStr = foreach.getTextTrim();
       if (forEachCollStr != null)
       {
       if (forEachCollStr.startsWith("#{"))
       {
       Object eval = JbpmExpressionEvaluator.evaluate(forEachCollStr, executionContext);
       if (eval == null)
       {
       throw new Exception("forEach expression '" + forEachCollStr + "' evaluates to null");
       }
      
       // expression evaluates to string
       if (eval instanceof String)
       {
       String[] forEachStrs = ((String)eval).trim().split(",");
       forEachColl = new ArrayList(forEachStrs.length);
       for (String forEachStr : forEachStrs)
       {
       forEachColl.add(forEachStr);
       }
       }
      
       // expression evaluates to collection
       else if (eval instanceof Collection)
       {
       forEachColl = (List)eval;
       }
       }
       }
       else
       {
       forEachColl = (List)FieldInstantiator.getValue(List.class, foreach);
       }
      
       if (var == null || var.length() == 0)
       {
       throw new Exception("forEach variable name has not been provided");
       }
      
       //
       // create forked paths
       //
      
       Token rootToken = executionContext.getToken();
       Node node = executionContext.getNode();
       List<ForkedTransition> forkTransitions = new ArrayList<ForkedTransition>();
      
       // first, create a new token and execution context for each item in list
       for (int i = 0; i < node.getLeavingTransitions().size(); i++)
       {
       Transition transition = (Transition) node.getLeavingTransitions().get(i);
      
       for (int iVar = 0; iVar < forEachColl.size(); iVar++)
       {
       // create child token to represent new path
       String tokenName = getTokenName(rootToken, transition.getName(), iVar);
       Token loopToken = new Token(rootToken, tokenName);
       loopToken.setTerminationImplicit(true);
       executionContext.getJbpmContext().getSession().save(loopToken);
      
       // assign variable within path
       final ExecutionContext newExecutionContext = new ExecutionContext(loopToken);
       newExecutionContext.getContextInstance().createVariable(var, forEachColl.get(iVar), loopToken);
      
       // record path & transition
       ForkedTransition forkTransition = new ForkedTransition();
       forkTransition.executionContext = newExecutionContext;
       forkTransition.transition = transition;
       forkTransitions.add(forkTransition);
       }
       }
      
       //
       // let each new token leave the node.
       //
       for (ForkedTransition forkTransition : forkTransitions)
       {
       node.leave(forkTransition.executionContext, forkTransition.transition);
       }
       }
      
       /**
       * Create a token name
       *
       * @param parent
       * @param transitionName
       * @return
       */
       protected String getTokenName(Token parent, String transitionName, int loopIndex)
       {
       String tokenName = null;
       if (transitionName != null)
       {
       if (!parent.hasChild(transitionName))
       {
       tokenName = transitionName;
       }
       else
       {
       int i = 2;
       tokenName = transitionName + Integer.toString(i);
       while (parent.hasChild(tokenName))
       {
       i++;
       tokenName = transitionName + Integer.toString(i);
       }
       }
       }
       else
       {
       // no transition name
       int size = ( parent.getChildren()!=null ? parent.getChildren().size()+1 : 1 );
       tokenName = Integer.toString(size);
       }
       return tokenName + "." + loopIndex;
       }
      
       /**
       * Fork Transition
       */
       private class ForkedTransition
       {
       private ExecutionContext executionContext;
       private Transition transition;
       }
      
      }