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 _______________________________________________ jboss-user mailing list jboss-user@lists.jboss.org https://lists.jboss.org/mailman/listinfo/jboss-user