### Eclipse Workspace Patch 1.0 #P jbpm4 Index: modules/api/src/main/resources/jpdl-4.4.xsd =================================================================== --- modules/api/src/main/resources/jpdl-4.4.xsd (revision 6394) +++ modules/api/src/main/resources/jpdl-4.4.xsd (working copy) @@ -1,8 +1,8 @@ @@ -219,6 +219,23 @@ + + Spawns concurrent paths of execution + over each element of a collection. + + + + + + + + + + + + + + Spawns multiple concurrent paths of execution. @@ -245,7 +262,7 @@ - + Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java =================================================================== --- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java (revision 6394) +++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java (working copy) @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.jbpm.api.Execution; import org.jbpm.api.activity.ActivityExecution; @@ -51,9 +52,8 @@ // evaluate the conditions and find the transitions that should be forked List forkingTransitions = new ArrayList(); - List outgoingTransitions = (List) activity.getOutgoingTransitions(); - for (TransitionImpl transition: outgoingTransitions) { - Condition condition = transition.getCondition(); + for (Transition transition: activity.getOutgoingTransitions()) { + Condition condition = ((TransitionImpl) transition).getCondition(); if ( (condition==null) || (condition.evaluate(execution)) ) { @@ -72,7 +72,7 @@ // if there are more transitions } else { - ExecutionImpl concurrentRoot = null; + ExecutionImpl concurrentRoot; if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) { concurrentRoot = execution; execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT); @@ -80,20 +80,22 @@ } else if (Execution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) { concurrentRoot = execution.getParent(); execution.end(); + } else { + // TODO is any other state possible? + concurrentRoot = execution; } - Map childExecutionsMap = new HashMap(); + Map concurrentExecutions = new HashMap(); for (Transition transition : forkingTransitions) { - // launch a concurrent path of execution - String childExecutionName = transition.getName(); - ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName); + ExecutionImpl concurrentExecution = concurrentRoot.createExecution(transition.getName()); concurrentExecution.setActivity(activity); concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT); - childExecutionsMap.put(transition, concurrentExecution); + concurrentExecutions.put(transition, concurrentExecution); } + - for (Transition transition : childExecutionsMap.keySet()) { - childExecutionsMap.get(transition).take(transition); + for (Entry entry : concurrentExecutions.entrySet()) { + entry.getValue().take(entry.getKey()); if (concurrentRoot.isEnded()) { break; Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java =================================================================== --- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java (revision 6394) +++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java (working copy) @@ -45,16 +45,16 @@ JoinActivity joinActivity = new JoinActivity(); if (element.hasAttribute(MULTIPLICITY)) { - String multiplicictyText = element.getAttribute(MULTIPLICITY); - Expression expression = Expression.create(multiplicictyText, Expression.LANGUAGE_UEL_VALUE); - joinActivity.setMultiplicityExpression(expression); + String multiplicityText = element.getAttribute(MULTIPLICITY); + Expression expression = Expression.create(multiplicityText, Expression.LANGUAGE_UEL_VALUE); + joinActivity.setMultiplicity(expression); } if (element.hasAttribute(LOCKMODE)) { String lockModeText = element.getAttribute(LOCKMODE); LockMode lockMode = LockMode.parse(lockModeText.toUpperCase()); if (lockMode==null) { - parse.addProblem(LOCKMODE + " " + lockModeText + " is not a valid lock mode", element); + parse.addProblem(lockModeText + " is not a valid lock mode", element); } else { joinActivity.setLockMode(lockMode); } Index: modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java =================================================================== --- modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java (revision 6394) +++ modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java (working copy) @@ -102,17 +102,14 @@ log.debug("create variable '"+key+"' in '"+this+"' with value '"+value+"'"); Type type = null; - - if (type==null) { - TypeSet typeSet = EnvironmentImpl.getFromCurrent(TypeSet.class, false); - if (typeSet!=null) { - if (typeName!=null) { - type = typeSet.findTypeByName(typeName); - } - if (type==null) { - type = typeSet.findTypeByMatch(key, value); - } + TypeSet typeSet = EnvironmentImpl.getFromCurrent(TypeSet.class, false); + if (typeSet!=null) { + if (typeName!=null) { + type = typeSet.findTypeByName(typeName); } + if (type==null) { + type = typeSet.findTypeByMatch(key, value); + } } Variable variable = null; @@ -241,7 +238,7 @@ } if (hasVariables) { for (Map.Entry entry: variables.entrySet()) { - String name = (String) entry.getKey(); + String name = entry.getKey(); Variable variable = entry.getValue(); Object value = variable.getValue(this); values.put(name, value); Index: modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java =================================================================== --- modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java (revision 0) +++ modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java (revision 0) @@ -0,0 +1,415 @@ +/* + * 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.test.activity.foreach; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import org.jbpm.api.Execution; +import org.jbpm.api.JbpmException; +import org.jbpm.api.ProcessInstance; +import org.jbpm.api.history.HistoryProcessInstance; +import org.jbpm.api.task.Task; +import org.jbpm.test.JbpmTestCase; + +/** + * @author Maciej Swiderski + */ +public class ForEachTest extends JbpmTestCase { + + public void testForEachLiteral() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachLiteral"); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertEquals("task1", taskAlex.getActivityName()); + taskService.completeTask(taskAlex.getId()); + + Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult(); + assertEquals("task1", taskMike.getActivityName()); + taskService.completeTask(taskMike.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + assertEquals(2, processInstance.getExecutions().size()); + + for (Execution exec : processInstance.getExecutions()) { + assertEquals(Execution.STATE_ACTIVE_CONCURRENT, exec.getState()); + executionService.signalExecutionById(exec.getId()); + } + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .uniqueResult(); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } + + public void testForEachList() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + Map variables = Collections.singletonMap("actors", Arrays.asList("alex", "mike")); + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachList", variables); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertEquals("task1", taskAlex.getActivityName()); + taskService.completeTask(taskAlex.getId()); + + Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult(); + assertEquals("task1", taskMike.getActivityName()); + taskService.completeTask(taskMike.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + assertEquals(2, processInstance.getExecutions().size()); + + for (Execution exec : processInstance.getExecutions()) { + assertEquals(Execution.STATE_ACTIVE_CONCURRENT, exec.getState()); + executionService.signalExecutionById(exec.getId()); + } + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .uniqueResult(); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } + + public void testForEachArray() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + Map variables = Collections.singletonMap("actors", new String[] { "alex", "mike" }); + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachArray", variables); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertEquals("task1", taskAlex.getActivityName()); + taskService.completeTask(taskAlex.getId()); + + Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult(); + assertEquals("task1", taskMike.getActivityName()); + taskService.completeTask(taskMike.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + assertEquals(2, processInstance.getExecutions().size()); + + for (Execution exec : processInstance.getExecutions()) { + assertEquals(Execution.STATE_ACTIVE_CONCURRENT, exec.getState()); + executionService.signalExecutionById(exec.getId()); + } + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .uniqueResult(); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } + + public void testForEachInvalid() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + Map variables = Collections.singletonMap("actors", new Date()); + try { + executionService.startProcessInstanceByKey("ForEachInvalid", variables); + fail("It should fail, since for-each list of items is a Date object"); + } + catch (JbpmException e) { + // expected result + } + } + + public void testForEachMissingVar() { + try { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + fail("expected foreach with missing variable to fail"); + } + catch (JbpmException e) { + // expected result + } + } + + public void testForEachJoinMultiplicity() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + Map variables = Collections.singletonMap("actors", Arrays.asList("alex", "mike")); + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachJoinMultiplicity", variables); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertEquals("task1", taskAlex.getActivityName()); + taskService.completeTask(taskAlex.getId()); + + Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult(); + assertNull(taskMike); + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .uniqueResult(); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } + + public void testForEachLiteralWithTransitionExpr() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachCondition"); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertEquals("task1", taskAlex.getActivityName()); + taskService.completeTask(taskAlex.getId()); + + Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult(); + assertEquals("task1", taskMike.getActivityName()); + taskService.completeTask(taskMike.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + assertEquals(2, processInstance.getExecutions().size()); + + for (Execution exec : processInstance.getExecutions()) { + assertEquals(Execution.STATE_ACTIVE_CONCURRENT, exec.getState()); + executionService.signalExecutionById(exec.getId()); + } + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .uniqueResult(); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } + + public void testForEachTooManyTransitions() { + try { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + fail("expected foreach with too many transitions"); + } + catch (JbpmException e) { + // expected result + } + } + + public void testForEachNoTransitions() { + try { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + fail("expected foreach with too many transitions"); + } + catch (JbpmException e) { + // expected result + } + } + + public void testForEachConditionTransitionsEvaluatedToFalse() { + try { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + Map variables = Collections.singletonMap("actors", Arrays.asList("alex", "mike")); + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachConditionFalse", variables); + + fail("expected foreach all conditions evaluated to false"); + } + catch (JbpmException e) { + // expected result + } + } +} Property changes on: modules\test-db\src\test\java\org\jbpm\test\activity\foreach\ForEachTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF 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 Property changes on: modules\examples\src\test\resources\org\jbpm\examples\concurrency\foreach\process.jpdl.xml ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java =================================================================== --- modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java (revision 0) +++ modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.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 ForEachTest 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 testForEachCompleteAll() { + + 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 testForEachCompleteAfterTwoJoined() { + + 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); + } +} Property changes on: modules\examples\src\test\java\org\jbpm\examples\concurrency\foreach\ForEachTest.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java =================================================================== --- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java (revision 0) +++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java (revision 0) @@ -0,0 +1,151 @@ +/* + * 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.jpdl.internal.activity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.jbpm.api.Execution; +import org.jbpm.api.JbpmException; +import org.jbpm.api.activity.ActivityExecution; +import org.jbpm.pvm.internal.el.Expression; +import org.jbpm.pvm.internal.env.Context; +import org.jbpm.pvm.internal.env.EnvironmentImpl; +import org.jbpm.pvm.internal.env.ExecutionContext; +import org.jbpm.pvm.internal.model.ActivityImpl; +import org.jbpm.pvm.internal.model.Condition; +import org.jbpm.pvm.internal.model.ExecutionImpl; +import org.jbpm.pvm.internal.model.TransitionImpl; + +/** + * @author Maciej Swiderski + * @author Alejandro Guizar + */ +public class ForEachActivity extends JpdlActivity { + + private String variable; + private Expression collection; + + private static final long serialVersionUID = 1L; + + public void execute(ActivityExecution execution) throws Exception { + execute((ExecutionImpl) execution); + } + + public void execute(ExecutionImpl execution) { + // resolve collection values + Collection collection = evaluateCollection(execution); + ActivityImpl activity = execution.getActivity(); + + // get concurrent root + ExecutionImpl concurrentRoot; + if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) { + concurrentRoot = execution; + execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT); + execution.setActivity(null); + } + else if (Execution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) { + concurrentRoot = execution.getParent(); + execution.end(); + } + else { + // TODO is any other state possible? + concurrentRoot = execution; + } + + // evaluate transition condition and create concurrent executions + TransitionImpl transition = activity.getDefaultOutgoingTransition(); + List concurrentExecutions = new ArrayList(); + int index = 1; + + //execution context needs to be temporarily replaced to give access to child execution variables + ExecutionContext originalExecutionContext = null; + ExecutionContext concurrentExecutionContext = null; + EnvironmentImpl environment = EnvironmentImpl.getCurrent(); + if (environment!=null) { + originalExecutionContext = (ExecutionContext) environment.removeContext(Context.CONTEXTNAME_EXECUTION); + } + + for (Object value : collection) { + ExecutionImpl concurrentExecution = concurrentRoot.createExecution(Integer + .toString(index++)); + concurrentExecution.setActivity(activity); + concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT); + concurrentExecution.createVariable(variable, value); + + // replace in the current environment execution context for expression evaluation purpose + concurrentExecutionContext = new ExecutionContext(concurrentExecution); + environment.setContext(concurrentExecutionContext); + + Condition condition = transition.getCondition(); + if (condition == null || condition.evaluate(concurrentExecution)) { + concurrentExecutions.add(concurrentExecution); + } + else { + concurrentExecution.end(); + } + } + // after all concurrent execution were processed reset original execution context + environment.setContext(originalExecutionContext); + + // if no concurrent executions should be launched + if (concurrentExecutions.isEmpty()) { + // throw exceptions to be consistent with decision activity + throw new JbpmException("no outgoing transition condition evaluated to true for " + activity); + } + else { + for (ExecutionImpl concurrentExecution : concurrentExecutions) { + concurrentExecution.take(transition); + if (concurrentRoot.isEnded()) break; + } + } + } + + private Collection evaluateCollection(ExecutionImpl execution) { + Object value = collection.evaluate(execution); + if (value instanceof Collection) { + // return collection verbatim + return (Collection) value; + } + else if (value instanceof Object[]) { + // wrap array in list + return Arrays.asList((Object[]) value); + } + else if (value instanceof String) { + // split string around commas or spaces + String csv = (String) value; + return Arrays.asList(csv.split("[,\\s]+")); + } + throw new JbpmException("not a collection: " + value); + } + + public void setVariable(String variable) { + this.variable = variable; + } + + public void setCollection(Expression collection) { + this.collection = collection; + } + +} Property changes on: modules\jpdl\src\main\java\org\jbpm\jpdl\internal\activity\ForEachActivity.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF Index: modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml =================================================================== --- modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml (revision 6394) +++ modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml (working copy) @@ -6,6 +6,7 @@ + Index: modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java =================================================================== --- modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java (revision 6394) +++ modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java (working copy) @@ -38,7 +38,7 @@ } public Method resolveFunction(String prefix, String localName) { - for (Method method: functionClass.getDeclaredMethods()) { + for (Method method: functionClass.getMethods()) { if (method.getName().equals(localName)) { return method; } Index: modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java =================================================================== --- modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java (revision 6394) +++ modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java (working copy) @@ -109,14 +109,15 @@ public abstract Object evaluateInScope(ScopeInstanceImpl scopeInstance); protected ELContext getElContext(ScopeInstanceImpl scopeInstance) { - ELContext elContext = (ELContext) (scopeInstance!=null ? scopeInstance.getElContext() : null); - if (elContext==null) { - JbpmElFactory contextFactory = JbpmElFactory.getJbpmElFactory(); - elContext = contextFactory.createElContext(scopeInstance); - if (scopeInstance!=null) { - scopeInstance.setElContext(elContext); - } + if (scopeInstance == null) { + return JbpmElFactory.getJbpmElFactory().createElContext(); } + + ELContext elContext = (ELContext) scopeInstance.getElContext(); + if (elContext == null) { + elContext = JbpmElFactory.getJbpmElFactory().createElContext(scopeInstance); + scopeInstance.setElContext(elContext); + } return elContext; } } Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java =================================================================== --- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java (revision 6394) +++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java (working copy) @@ -22,6 +22,7 @@ package org.jbpm.jpdl.internal.activity; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.hibernate.LockMode; @@ -33,9 +34,9 @@ import org.jbpm.api.model.Transition; import org.jbpm.pvm.internal.el.Expression; import org.jbpm.pvm.internal.env.EnvironmentImpl; +import org.jbpm.pvm.internal.model.ActivityImpl; import org.jbpm.pvm.internal.model.ExecutionImpl; - /** * @author Tom Baeyens */ @@ -43,26 +44,16 @@ private static final long serialVersionUID = 1L; - int multiplicity = -1; - LockMode lockMode = LockMode.UPGRADE; - Expression multiplicityExpression; + private LockMode lockMode = LockMode.UPGRADE; + private Expression multiplicity; public void execute(ActivityExecution execution) { execute((ExecutionImpl)execution); } public void execute(ExecutionImpl execution) { - Activity activity = execution.getActivity(); + ActivityImpl activity = execution.getActivity(); - // evaluate multiplicity expression - if (multiplicityExpression != null) { - try { - multiplicity = Integer.valueOf(multiplicityExpression.evaluate(execution).toString()); - } catch (Exception e) { - throw new JbpmException("Problem while evaluating multiplicity attribute: " + e.getMessage()); - } - } - // if this is a single, non concurrent root if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) { // just pass through @@ -84,23 +75,22 @@ ExecutionImpl concurrentRoot = execution.getParent(); List joinedExecutions = getJoinedExecutions(concurrentRoot, activity); - if (isComplete(joinedExecutions, activity)) { - endJoinedExecutions(joinedExecutions); - - if (multiplicity != -1) { - // remove forked but not joined executions only when multiplicity attribute was used - List forkedExecutionsToRemove = new ArrayList(); + if (isComplete(execution, joinedExecutions)) { + endExecutions(joinedExecutions); + // if multiplicity was used + if (multiplicity != null) { + // collect concurrent executions still active + List danglingExecutions = new ArrayList(); for (ExecutionImpl concurrentExecution : concurrentRoot.getExecutions()) { - //collect all executions from the parent that are forked - if ( (Execution.STATE_ACTIVE_CONCURRENT.equals(concurrentExecution.getState()))) { - forkedExecutionsToRemove.add(concurrentExecution); + if (Execution.STATE_ACTIVE_CONCURRENT.equals(concurrentExecution.getState())) { + danglingExecutions.add(concurrentExecution); } } - // end all of found forked (but not joined) executions - endJoinedExecutions(forkedExecutionsToRemove); + // end dangling executions + endExecutions(danglingExecutions); } ExecutionImpl outgoingExecution = null; - if (concurrentRoot.getExecutions().size()==0) { + if (concurrentRoot.getExecutions().isEmpty()) { outgoingExecution = concurrentRoot; outgoingExecution.setState(Execution.STATE_ACTIVE_ROOT); } else { @@ -120,19 +110,22 @@ throw new JbpmException("invalid execution state"); } } - - protected boolean isComplete(List joinedExecutions, Activity activity) { - int nbrOfExecutionsToJoin = multiplicity; - if (multiplicity==-1) { - nbrOfExecutionsToJoin = activity.getIncomingTransitions().size(); + + protected boolean isComplete(ExecutionImpl execution, List joinedExecutions) { + int executionsToJoin; + if (multiplicity != null) { + executionsToJoin = evaluateMultiplicity(execution); } - return joinedExecutions.size()==nbrOfExecutionsToJoin; + else { + executionsToJoin = execution.getActivity().getIncomingTransitions().size(); + } + return joinedExecutions.size() == executionsToJoin; } protected List getJoinedExecutions(ExecutionImpl concurrentRoot, Activity activity) { List joinedExecutions = new ArrayList(); - List concurrentExecutions = (List)concurrentRoot.getExecutions(); - for (ExecutionImpl concurrentExecution: (List)concurrentExecutions) { + Collection concurrentExecutions = concurrentRoot.getExecutions(); + for (ExecutionImpl concurrentExecution: concurrentExecutions) { if ( (Execution.STATE_INACTIVE_JOIN.equals(concurrentExecution.getState())) && (concurrentExecution.getActivity()==activity) ) { @@ -142,16 +135,30 @@ return joinedExecutions; } - protected void endJoinedExecutions(List joinedExecutions) { - for (ExecutionImpl joinedExecution: joinedExecutions) { - joinedExecution.end(); + protected void endExecutions(List executions) { + for (ExecutionImpl execution: executions) { + execution.end(); } } + private int evaluateMultiplicity(ExecutionImpl execution) { + if (multiplicity != null) { + Object value = multiplicity.evaluate(execution); + if (value instanceof Number) { + Number number = (Number) value; + return number.intValue(); + } + if (value instanceof String) { + return Integer.parseInt((String) value); + } + } + return -1; + } + public void setLockMode(LockMode lockMode) { this.lockMode = lockMode; } - public void setMultiplicityExpression(Expression multiplicityExpression) { - this.multiplicityExpression = multiplicityExpression; + public void setMultiplicity(Expression multiplicity) { + this.multiplicity = multiplicity; } } Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java =================================================================== --- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java (revision 0) +++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java (revision 0) @@ -0,0 +1,108 @@ +/* + * 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.jpdl.internal.activity; + +import java.util.List; + +import org.jbpm.jpdl.internal.xml.JpdlParser; +import org.jbpm.pvm.internal.el.Expression; +import org.jbpm.pvm.internal.model.ActivityImpl; +import org.jbpm.pvm.internal.model.ExpressionCondition; +import org.jbpm.pvm.internal.model.TransitionImpl; +import org.jbpm.pvm.internal.util.XmlUtil; +import org.jbpm.pvm.internal.wire.usercode.UserCodeCondition; +import org.jbpm.pvm.internal.wire.usercode.UserCodeReference; +import org.jbpm.pvm.internal.xml.Parse; +import org.w3c.dom.Element; + +/** + * @author Alejandro Guizar + */ +public class ForEachBinding extends JpdlBinding { + + private static final String VARIABLE = "var"; + private static final String COLLECTION = "in"; + + public ForEachBinding() { + super("foreach"); + } + + @Override + public Object parseJpdl(Element element, Parse parse, JpdlParser parser) { + ForEachActivity activity = new ForEachActivity(); + + if (element.hasAttribute(VARIABLE)) { + activity.setVariable(element.getAttribute(VARIABLE)); + } + else { + parse.addProblem(VARIABLE + " attribute missing", element); + } + + if (element.hasAttribute(COLLECTION)) { + Expression collection = Expression + .create(element.getAttribute(COLLECTION), Expression.LANGUAGE_UEL_VALUE); + activity.setCollection(collection); + } + else { + parse.addProblem(COLLECTION + " attribute missing", element); + } + + // process transition elements + List transitionElements = XmlUtil.elements(element, "transition"); + + if (transitionElements.size() != 1) { + parse.addProblem("foreach activity can/must have one outgoing transition, found "+ transitionElements.size() + " transitions ", element); + } else { + + ActivityImpl activityFromStack = parse.contextStackFind(ActivityImpl.class); + TransitionImpl transition = activityFromStack.getDefaultOutgoingTransition(); + + // get first transition + Element transitionElement = transitionElements.get(0); + + Element conditionElement = XmlUtil.element(transitionElement, "condition"); + if (conditionElement != null) { + + if (conditionElement.hasAttribute("expr")) { + ExpressionCondition expressionCondition = new ExpressionCondition(); + expressionCondition.setExpression(conditionElement.getAttribute("expr")); + expressionCondition.setLanguage(XmlUtil.attribute(conditionElement, "lang")); + transition.setCondition(expressionCondition); + + } else { + Element conditionHandlerElement = XmlUtil.element(conditionElement, "handler"); + if (conditionHandlerElement != null) { + UserCodeCondition userCodeCondition = new UserCodeCondition(); + + UserCodeReference conditionReference = parser.parseUserCodeReference(conditionHandlerElement, parse); + userCodeCondition.setConditionReference(conditionReference); + + transition.setCondition(userCodeCondition); + } + } + } + } + + return activity; + } + +} Property changes on: modules\jpdl\src\main\java\org\jbpm\jpdl\internal\activity\ForEachBinding.java ___________________________________________________________________ Added: svn:keywords + Id Revision Added: svn:eol-style + LF