[jboss-user] [JBoss jBPM] - Parallel Review Process: template process definition

alf_dave do-not-reply at jboss.com
Wed Oct 18 13:06:57 EDT 2006


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

View the original post : http://www.jboss.com/index.html?module=bb&op=viewtopic&p=3979150#3979150

Reply to the post : http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&p=3979150



More information about the jboss-user mailing list