### Eclipse Workspace Patch 1.0
#P jbpm4
Index: modules/userguide/src/main/docbook/en/images/process.concurrency.foreachfork.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: modules/userguide/src/main/docbook/en/images/process.concurrency.foreachfork.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Index: modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml
===================================================================
--- modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml (revision 0)
+++ modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml (revision 0)
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: modules/userguide/src/main/docbook/en/modules/ch06-Jpdl.xml
===================================================================
--- modules/userguide/src/main/docbook/en/modules/ch06-Jpdl.xml (revision 6373)
+++ modules/userguide/src/main/docbook/en/modules/ch06-Jpdl.xml (working copy)
@@ -498,6 +498,40 @@
With the fork and join activities,
concurrent paths of executions can be modeled.
+
fork attributes:
+
+
+
+ Attribute
+ Type
+ Default
+ Required?
+ Description
+
+
+
+
+ for-each
+ expression or text
+ list of items to iterate over
+ optional
+ The list of items for which execution should be multiplied. Each element from this list will
+ create new execution for every transition defined on fork activity. 'for-each' must be used together with 'var'.
+ for-each attribute supports: any type of collection, arrays and comma separated strings.
+
+
+
+ var
+ text
+ variable name used for item when iterating over for-each list
+ optional
+ The name of the variable used as a current item of the for-each list. This variable will
+ be set on execution and will be visible only to that execution. 'var' must be used together with 'for-each'
+
+
+
+
+
join attributes:
@@ -512,7 +546,7 @@
multiplicity
- integer
+ integer or expressionnbr of incoming transitionsoptionalThe number of executions that should arrive in this join
@@ -578,6 +612,52 @@
<end name="end" />
</process>
+
+
+ Multiplying fork transition - for each support
+
+ In some cases, there is a need to multiply same tasks but they should be assigned to different people/groups.
+ As shown in the example, there is a need to collect time sheet reports (called data) from different departments.
+ With for each syntax it can be quite easily achieved. It is the same task but should be performed by different groups.
+ Groups are defined as process variable listOfDepartments and the same is for how many tasks must
+ be completed before execution will leave join activity joinAt.
+
+
+
+<process name="ForEachFork" xmlns="http://jbpm.org/4.3/jpdl">
+ <start name="start1">
+ <transition to="fork1"/>
+ </start>
+ <fork for-each="#{listOfDepartments}" name="fork1" var="department">
+ <transition to="Collect data"/>
+ </fork>
+ <task candidate-groups="#{department}" name="Collect data">
+ <transition to="join1"/>
+ </task>
+ <join multiplicity="#{joinAt}" name="join1">
+ <transition to="end1"/>
+ </join>
+ <end name="end1"/>
+</process>
+
+
+ Important thing to notice: when using for-each attribute on fork activity, join activity must have multiplicity attribute.
+ In general, join activity will continue execution baesd on incoming transitions, since when using for-each there will be
+ only one incoming transition defined multiplicity attribute must indicate when join should move on.
+ Otherwise first execution that reaches the join activity will trigger continuation.
+
+
+HashMap<String, Object> variables = new HashMap<String, Object>();
+variables.put("listOfDepartments", new String[] { "sales-dept", "hr-dept", "finance-dept" });
+variables.put("joinAt", 3);
+
+ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", variables);
+
+
+
Index: modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachForkTest.java
===================================================================
--- modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachForkTest.java (revision 0)
+++ modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachForkTest.java (revision 0)
@@ -0,0 +1,166 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.examples.concurrency.foreach;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import org.jbpm.api.ProcessInstance;
+import org.jbpm.api.task.Task;
+import org.jbpm.test.JbpmTestCase;
+
+/**
+ * @author Tom Baeyens
+ */
+public class ForEachForkTest extends JbpmTestCase {
+
+ String deploymentId;
+ String deptSales;
+ String deptHR;
+ String deptFinance;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // create identities
+ deptSales = identityService.createGroup("sales-dept");
+ deptHR = identityService.createGroup("hr-dept");
+ deptFinance = identityService.createGroup("finance-dept");
+
+ identityService.createUser("johndoe", "John", "Doe");
+ identityService.createMembership("johndoe", deptSales, "SalesManager");
+
+ identityService.createUser("joesmoe", "Joe", "Smoe");
+ identityService.createMembership("joesmoe", deptHR, "HRManager");
+
+ identityService.createUser("janedoe", "Jane", "Doe");
+ identityService.createMembership("janedoe", deptFinance, "FinanceManager");
+
+ deploymentId = repositoryService.createDeployment().addResourceFromClasspath("org/jbpm/examples/concurrency/foreach/process.jpdl.xml").deploy();
+ }
+
+ protected void tearDown() throws Exception {
+ repositoryService.deleteDeploymentCascade(deploymentId);
+
+ // delete identities
+ identityService.deleteGroup(deptSales);
+ identityService.deleteGroup(deptHR);
+ identityService.deleteGroup(deptFinance);
+ identityService.deleteUser("johndoe");
+ identityService.deleteUser("joesmoe");
+ identityService.deleteUser("janedoe");
+
+ super.tearDown();
+ }
+
+ public void testForEachForkCompleteAll() {
+
+ HashMap variables = new HashMap();
+ variables.put("listOfDepartments", new String[] { "sales-dept", "hr-dept", "finance-dept" });
+ variables.put("joinAt", 3);
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", variables);
+ String processInstanceId = processInstance.getId();
+
+ // there should be 3 forked executions - same as number of departments
+ assertEquals(3, processInstance.getExecutions().size());
+
+ List taskListSales = taskService.findGroupTasks("johndoe");
+ assertEquals("Expected a single task in johndoe's task list", 1, taskListSales.size());
+
+ List taskListHR = taskService.findGroupTasks("joesmoe");
+ assertEquals("Expected a single task in joesmoe's task list", 1, taskListHR.size());
+
+ List taskListFinance = taskService.findGroupTasks("janedoe");
+ assertEquals("Expected a single task in janedoe's task list", 1, taskListFinance.size());
+
+ // a member of sales department takes the task
+ taskService.takeTask(taskListSales.get(0).getId(), "johndoe");
+
+ taskListSales = taskService.findPersonalTasks("johndoe");
+ assertEquals("Expected a single task being created", 1, taskListSales.size());
+ // complete collect data from sales department
+ taskService.completeTask(taskListSales.get(0).getId());
+
+ // next a member of HR department takes the task
+ taskService.takeTask(taskListHR.get(0).getId(), "joesmoe");
+
+ taskListHR = taskService.findPersonalTasks("joesmoe");
+ assertEquals("Expected a single task being created", 1, taskListHR.size());
+ // complete collect data from HR department
+ taskService.completeTask(taskListHR.get(0).getId());
+
+ // finally a member of Finance department takes the task
+ taskService.takeTask(taskListFinance.get(0).getId(), "janedoe");
+
+ taskListFinance = taskService.findPersonalTasks("janedoe");
+ assertEquals("Expected a single task being created", 1, taskListFinance.size());
+ // complete collect data from HR department
+ taskService.completeTask(taskListFinance.get(0).getId());
+
+ Date endTime = historyService.createHistoryProcessInstanceQuery().processInstanceId(processInstance.getId()).uniqueResult().getEndTime();
+
+ assertNotNull(endTime);
+ }
+
+ public void testForEachForkCompleteAfterTwoJoined() {
+
+ HashMap variables = new HashMap();
+ variables.put("listOfDepartments", new String[] { "sales-dept", "hr-dept", "finance-dept" });
+ variables.put("joinAt", 2);
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", variables);
+
+ // there should be 3 forked executions - same as number of departments
+ assertEquals(3, processInstance.getExecutions().size());
+
+ List taskListSales = taskService.findGroupTasks("johndoe");
+ assertEquals("Expected a single task in johndoe's task list", 1, taskListSales.size());
+
+ List taskListHR = taskService.findGroupTasks("joesmoe");
+ assertEquals("Expected a single task in joesmoe's task list", 1, taskListHR.size());
+
+ List taskListFinance = taskService.findGroupTasks("janedoe");
+ assertEquals("Expected a single task in janedoe's task list", 1, taskListFinance.size());
+
+ // a member of sales department takes the task
+ taskService.takeTask(taskListSales.get(0).getId(), "johndoe");
+
+ taskListSales = taskService.findPersonalTasks("johndoe");
+ assertEquals("Expected a single task being created", 1, taskListSales.size());
+ // complete collect data from sales department
+ taskService.completeTask(taskListSales.get(0).getId());
+
+ // next a member of HR department takes the task
+ taskService.takeTask(taskListHR.get(0).getId(), "joesmoe");
+
+ taskListSales = taskService.findPersonalTasks("joesmoe");
+ assertEquals("Expected a single task being created", 1, taskListSales.size());
+ // complete collect data from HR department
+ taskService.completeTask(taskListSales.get(0).getId());
+
+ Date endTime = historyService.createHistoryProcessInstanceQuery().processInstanceId(processInstance.getId()).uniqueResult().getEndTime();
+
+ assertNotNull(endTime);
+ }
+}