[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