### 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. + <literal>fork</literal> 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' + + + + +
<literal>join</literal> attributes: @@ -512,7 +546,7 @@ multiplicity - integer + integer or expression nbr of incoming transitions optional The 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. + +
+ The for each fork example process + +
+ +<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); + } +}