[jbpm-commits] JBoss JBPM SVN: r6397 - in jbpm4/trunk/modules: examples/src/test/java/org/jbpm/examples/concurrency and 9 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Wed Jun 9 13:46:56 EDT 2010


Author: swiderski.maciej
Date: 2010-06-09 13:46:55 -0400 (Wed, 09 Jun 2010)
New Revision: 6397

Added:
   jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/
   jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java
   jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/
   jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/foreach/
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java
Modified:
   jbpm4/trunk/modules/api/src/main/resources/jpdl-4.4.xsd
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java
   jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java
   jbpm4/trunk/modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java
   jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java
Log:
JBPM-2414: for each activity

Modified: jbpm4/trunk/modules/api/src/main/resources/jpdl-4.4.xsd
===================================================================
--- jbpm4/trunk/modules/api/src/main/resources/jpdl-4.4.xsd	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/api/src/main/resources/jpdl-4.4.xsd	2010-06-09 17:46:55 UTC (rev 6397)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <schema xmlns="http://www.w3.org/2001/XMLSchema"
-        targetNamespace="http://jbpm.org/4.4/jpdl"
-        xmlns:tns="http://jbpm.org/4.4/jpdl"
+        targetNamespace="http://jbpm.org/jpdl/4.4"
+        xmlns:tns="http://jbpm.org/jpdl/4.4"
         elementFormDefault="qualified"
         attributeFormDefault="unqualified">
         
@@ -219,6 +219,23 @@
       </element>
 
       <!-- ~~~ FORK ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+      <element name="foreach">
+        <annotation><documentation>Spawns concurrent paths of execution
+          over each element of a collection.
+        </documentation></annotation>
+        <complexType>
+          <sequence>
+            <element name="description" minOccurs="0" type="string" />
+            <element ref="tns:on" minOccurs="0" maxOccurs="unbounded"/>
+            <element ref="tns:transition" minOccurs="0" maxOccurs="unbounded" />
+          </sequence>
+          <attributeGroup ref="tns:activityAttributes" />
+          <attribute name="var" type="string" default="var"/>
+          <attribute name="in" type="string" use="required"/>
+        </complexType>
+      </element>
+
+      <!-- ~~~ FORK ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
       <element name="fork">
         <annotation><documentation>Spawns multiple concurrent paths of 
         execution.
@@ -245,7 +262,7 @@
             <element ref="tns:transition" minOccurs="0" maxOccurs="unbounded" />
           </sequence>
           <attributeGroup ref="tns:activityAttributes" />
-          <attribute name="multiplicity" type="int" />
+          <attribute name="multiplicity" type="string" />
           <attribute name="lockmode" default="upgrade">
             <simpleType>
               <restriction base="string">

Added: jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java
===================================================================
--- jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java	                        (rev 0)
+++ jbpm4/trunk/modules/examples/src/test/java/org/jbpm/examples/concurrency/foreach/ForEachTest.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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, "HRMana!
 ger");
+
+    identityService.createUser("janedoe", "Jane", "D!
 oe");
+ 
   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<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.startProc!
 essInstanceByKey("ForEachFork", variables);
+    String processInstanceId = processInstance.getId();
+
+    // there should be 3 forked executions - same as number of departments
+    assertEquals(3, processInstance.getExecutions().size());
+
+    List<Task> taskListSales = taskService.findGroupTasks("johndoe");
+    assertEquals("Expected a single task in johndoe's task list", 1, taskListSales.size());
+
+    List<Task> taskListHR = taskService.findGroupTasks("joesmoe");
+    assertEquals("Expected a single task in joesmoe's task list", 1, taskListHR.size());
+
+    List<Task> 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, tas!
 kListSales.size());
+    // complete collect data from sales d!
 epartmen
t
+    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<String, Object> variables = new HashMap<String, Object>();
+    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<Task> taskListSales = taskService.findGroupTasks("johndoe");
+    assertEquals("Expected a single task in johndoe's task list", 1, taskListSales.size());
+
+    List<Task> taskListHR = taskService.findGroupTasks("joesmoe");
+    assertEquals("Expected a single task in joesmoe's task list", 1, taskListHR.size());
+
+    List<Task> taskListFinance = taskService.findGroupTasks("janedoe");
+    assertEquals("Expected a single task in j!
 anedoe's task list", 1, taskListFinance.size());
+
+    // a m!
 ember 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);
+  }
+}
Added: jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml
===================================================================
--- jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml	                        (rev 0)
+++ jbpm4/trunk/modules/examples/src/test/resources/org/jbpm/examples/concurrency/foreach/process.jpdl.xml	2010-06-09 17:46:55 UTC (rev 6397)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<process name="ForEachFork" xmlns="http://jbpm.org/4.3/jpdl">
+   <start g="28,61,48,48" name="start1">
+      <transition to="foreach1"/>
+   </start>
+   <foreach var="department" in="#{listOfDepartments}" g="111,60,48,48" name="foreach1">
+      <transition to="Collect data"/>
+   </foreach>
+   <task candidate-groups="#{department}" g="201,58,92,52" name="Collect data">
+      <transition to="join1"/>
+   </task>
+   <join g="343,59,48,48" multiplicity="#{joinAt}" name="join1">
+      <transition to="end1"/>
+   </join>
+   <end g="433,60,48,48" name="end1"/>
+</process>
\ No newline at end of file

Added: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java	                        (rev 0)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachActivity.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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(ActivityExecu!
 tion execution) throws Exception {
+    execute((ExecutionImpl!
 ) execut
ion);
+  }
+
+  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<ExecutionImpl> concurrentExecutions = new ArrayList<ExecutionImpl>();
+    int in!
 dex = 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 = t!
 ransitio
n.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 instanceo!
 f 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;
+  }
+
+}
Added: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java	                        (rev 0)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForEachBinding.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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<Element> 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 tr!
 ansition
+      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(conditionRe!
 ference);
+  
+            transition.setCondition(userCodeCon!
 dition);

+          }
+        }
+      }
+    }
+    
+    return activity;
+  }
+
+}
Modified: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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<Transition> forkingTransitions = new ArrayList<Transition>();
-    List<TransitionImpl> 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<Transition, ExecutionImpl> childExecutionsMap = new HashMap<Transition, ExecutionImpl>();
+      Map<Transition, ExecutionImpl> concurrentExecutions = new HashMap<Transition, ExecutionImpl>();
       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<Transition, ExecutionImpl> entry : concurrentExecutions.entrySet()) {
+        entry.getValue().take(entry.getKey());
 
         if (concurrentRoot.isEnded()) {
           break;

Modified: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinActivity.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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<ExecutionImpl> 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<ExecutionImpl> forkedExecutionsToRemove = new ArrayList<ExecutionImpl>();
+      if (isComplete(execution, joinedExecutions)) {
+        endExecutions(joinedExecutions);
+        // if multiplicity was used
+        if (multiplicity != null) {
+          // collect concurrent executions still active
+          List<ExecutionImpl> danglingExecutions = new ArrayList<ExecutionImpl>();
           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<ExecutionImpl> joinedExecutions, Activity activity) {
-    int nbrOfExecutionsToJoin = multiplicity;
-    if (multiplicity==-1) {
-      nbrOfExecutionsToJoin = activity.getIncomingTransitions().size();
+
+  protected boolean isComplete(ExecutionImpl execution, List<ExecutionImpl> 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<ExecutionImpl> getJoinedExecutions(ExecutionImpl concurrentRoot, Activity activity) {
     List<ExecutionImpl> joinedExecutions = new ArrayList<ExecutionImpl>();
-    List concurrentExecutions = (List)concurrentRoot.getExecutions();
-    for (ExecutionImpl concurrentExecution: (List<ExecutionImpl>)concurrentExecutions) {
+    Collection<ExecutionImpl> 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<ExecutionImpl> joinedExecutions) {
-    for (ExecutionImpl joinedExecution: joinedExecutions) {
-      joinedExecution.end();
+  protected void endExecutions(List<ExecutionImpl> 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;
   }
 }

Modified: jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/JoinBinding.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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);
       }

Modified: jbpm4/trunk/modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml
===================================================================
--- jbpm4/trunk/modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/jpdl/src/main/resources/jbpm.jpdl.bindings.xml	2010-06-09 17:46:55 UTC (rev 6397)
@@ -6,6 +6,7 @@
   <activity binding="org.jbpm.jpdl.internal.activity.EndBinding" />
   <activity binding="org.jbpm.jpdl.internal.activity.EndCancelBinding" />
   <activity binding="org.jbpm.jpdl.internal.activity.EndErrorBinding" />
+  <activity binding="org.jbpm.jpdl.internal.activity.ForEachBinding" />
   <activity binding="org.jbpm.jpdl.internal.activity.ForkBinding" />
   <activity binding="org.jbpm.jpdl.internal.activity.JoinBinding" />
   <activity binding="org.jbpm.jpdl.internal.activity.HqlBinding" />

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/Expression.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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;
   }
 }

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/el/JbpmFunctionMapper.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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;
       }

Modified: jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java
===================================================================
--- jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java	2010-06-09 17:35:57 UTC (rev 6396)
+++ jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/model/ScopeInstanceImpl.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -248,7 +248,7 @@
     }
     if (hasVariables) {
       for (Map.Entry<String, Variable> 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);

Added: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java	                        (rev 0)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/foreach/ForEachTest.java	2010-06-09 17:46:55 UTC (rev 6397)
@@ -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(""
+      + "<process name='ForEachLiteral' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,17,32,29' name='start1'>"
+      + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='alex, mike'>"
+      + "      <transition name='left' to='task1' g='-44,-18'/>"
+      + "   </foreach>"
+!
       + "   <task name='task1' g='90,177,73,44' assignee='#{as!
 sign}'>"

+      + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+      + "   </task>"
+      + "   <state name='Big car' > "
+      + "     <transition name='to join2' to='join2' g='-43,-18'/>"
+      + "   </state> "
+      + "   <join name='join2' g='192,511,57,44' multiplicity='2'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    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.f!
 indProcessInstanceById(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(""
+      + "<process name='ForEachList' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,17,32,29' name='start1'>"
+      + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='#{actors}'>"
+  !
     + "      <transition name='left' to='task1' g='-44,-18'/>"!
 
+      
+ "   </foreach>"
+      + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+      + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+      + "   </task>"
+      + "   <state name='Big car' > "
+      + "     <transition name='to join2' to='join2' g='-43,-18'/>"
+      + "   </state> "
+      + "   <join name='join2' g='192,511,57,44' multiplicity='#{actors.size()}'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    Map<String, ?> 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(""
+      + "<process name='ForEachArray' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,!
 17,32,29' name='start1'>"
+      + "      <transition g='-43,-!
 18' name
='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='#{actors}'>"
+      + "      <transition name='left' to='task1' g='-44,-18'/>"
+      + "   </foreach>"
+      + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+      + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+      + "   </task>"
+      + "   <state name='Big car' > "
+      + "   <transition name='to join2' to='join2' g='-43,-18'/>"
+      + "   </state> "
+      + "   <join name='join2' g='192,511,57,44' multiplicity='#{length(actors)}'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    Map<String, ?> 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());
+    assertEqu!
 als("end1", history.getEndActivityName());
+  }
+
+  public vo!
 id testF
orEachInvalid() {
+    deployJpdlXmlString(""
+      + "<process name='ForEachInvalid' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,17,32,29' name='start1'>"
+      + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='#{actors}'>"
+      + "      <transition name='left' to='task1' g='-44,-18'/>"
+      + "   </foreach>"
+      + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+      + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+      + "   </task>"
+      + "   <state name='Big car' > "
+      + "   <transition name='to join2' to='join2' g='-43,-18'/>"
+      + "   </state> "
+      + "   <join name='join2' g='192,511,57,44'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    Map<String, ?> vari!
 ables = 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(""
+        + "<process name='ForEachMissingVar' xmlns='http://jbpm.org/jpdl/4.4'>"
+        + "   <start g='179,17,32,29' name='start1'>"
+        + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+        + "   </start>"
+        + "   <foreach g='185,95,49,50' name='foreach1' in='#{actors}' >"
+        + "      <transition name='left' to='task1' g='-44,-18'/>"
+        + "   </foreach>"
+        + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+        + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+        + "   </task>"
+        + "   <state !
 name='Big car' > "
+        + "   <transition name='to join2' !
 to='join
2' g='-43,-18'/>"
+        + "   </state> "
+        + "   <join name='join2' g='192,511,57,44'>"
+        + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+        + "   </join>"
+        + "   <end g='193,606,38,33' name='end1'/>"
+        + "</process>");
+
+      fail("expected foreach with missing variable to fail");
+    }
+    catch (JbpmException e) {
+      // expected result
+    }
+  }
+
+  public void testForEachJoinMultiplicity() {
+    deployJpdlXmlString(""
+      + "<process name='ForEachJoinMultiplicity' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,17,32,29' name='start1'>"
+      + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='#{actors}'>"
+      + "      <transition name='left' to='task1' g='-44,-18'/>"
+      + "   </foreach>"
+      + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+      + "     !
  <transition name='to state' to='join2' g='-43,-18'/>"
+      + "   </task>"
+      + "   <join name='join2' g='192,511,57,44' multiplicity='#{actors.size() - 1}'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    Map<String, ?> 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(ProcessIns!
 tance.ST
ATE_ENDED, history.getState());
+    assertEquals("end1", history.getEndActivityName());
+  }
+  
+  public void testForEachLiteralWithTransitionExpr() {
+    deployJpdlXmlString(""
+      + "<process name='ForEachCondition' xmlns='http://jbpm.org/jpdl/4.4'>"
+      + "   <start g='179,17,32,29' name='start1'>"
+      + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+      + "   </start>"
+      + "   <foreach g='185,95,49,50' name='foreach1' var='assign' in='alex, mike, peter'>"
+      + "      <transition name='left' to='task1' g='-44,-18'>"
+      + "           <condition expr='#{assign==&quot;alex&quot; or assign==&quot;mike&quot;}' /> "
+      + "      </transition>" 
+      + "   </foreach>"
+      + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+      + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+      + "   </task>"
+      + "   <state name='Big car' > "
+      + "     <transition name='to join2' to='join2' g='!
 -43,-18'/>"
+      + "   </state> "
+      + "   <join name='join2' g='192,511,57,44' multiplicity='2'>"
+      + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+      + "   </join>"
+      + "   <end g='193,606,38,33' name='end1'/>"
+      + "</process>");
+
+    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()) {
+      assert!
 Equals(Execution.STATE_ACTIVE_CONCURRENT, exec.getState());
+ !
      exe
cutionService.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(""
+        + "<process name='ForEachTooManyTransitions' xmlns='http://jbpm.org/jpdl/4.4'>"
+        + "   <start g='179,17,32,29' name='start1'>"
+        + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+        + "   </start>"
+        + "   <foreach g='185,95,49,50' name='foreach1' in='#{actors}' var='assign'>"
+        + "      <transition name='left' to='task1' g='-44,-18'/>"
+        + "      <transition name='right' to='task1' g='-44,-18'/>"
+        + "   </foreach>"
+        + "   <task name='task1' g='90,177,7!
 3,44' assignee='#{assign}'>"
+        + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+        + "   </task>"
+        + "   <state name='Big car' > "
+        + "   <transition name='to join2' to='join2' g='-43,-18'/>"
+        + "   </state> "
+        + "   <join name='join2' g='192,511,57,44'>"
+        + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+        + "   </join>"
+        + "   <end g='193,606,38,33' name='end1'/>"
+        + "</process>");
+
+      fail("expected foreach with too many transitions");
+    }
+    catch (JbpmException e) {
+      // expected result
+    }
+  }
+  
+  public void testForEachNoTransitions() {
+    try {
+      deployJpdlXmlString(""
+        + "<process name='ForEachNoTransition' xmlns='http://jbpm.org/jpdl/4.4'>"
+        + "   <start g='179,17,32,29' name='start1'>"
+        + "      <transition g='-43,-18' name='to foreach1' to='foreach1'/>"
+        + "   </start>"
+        + "   <foreach g='185,!
 95,49,50' name='foreach1' in='#{actors}' var='assign' >"
+    !
     + " 
  </foreach>"
+        + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+        + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+        + "   </task>"
+        + "   <state name='Big car' > "
+        + "   <transition name='to join2' to='join2' g='-43,-18'/>"
+        + "   </state> "
+        + "   <join name='join2' g='192,511,57,44'>"
+        + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+        + "   </join>"
+        + "   <end g='193,606,38,33' name='end1'/>"
+        + "</process>");
+
+      fail("expected foreach with too many transitions");
+    }
+    catch (JbpmException e) {
+      // expected result
+    }
+  }
+  
+  public void testForEachConditionTransitionsEvaluatedToFalse() {
+    try {
+      deployJpdlXmlString(""
+        + "<process name='ForEachConditionFalse' xmlns='http://jbpm.org/jpdl/4.4'>"
+        + "   <start g='179,17,32,29' name='start1'>"
+        + "      <transition g='-43,-18' name='to !
 foreach1' to='foreach1'/>"
+        + "   </start>"
+        + "   <foreach g='185,95,49,50' name='foreach1' in='#{actors}' var='assign' >"
+        + "      <transition name='left' to='task1' g='-44,-18'>"
+        + "           <condition expr='#{assign==&quot;peter&quot;}' /> "
+        + "      </transition>" 
+        + "   </foreach>"
+        + "   <task name='task1' g='90,177,73,44' assignee='#{assign}'>"
+        + "      <transition name='to state' to='Big car' g='-43,-18'/>"
+        + "   </task>"
+        + "   <state name='Big car' > "
+        + "   <transition name='to join2' to='join2' g='-43,-18'/>"
+        + "   </state> "
+        + "   <join name='join2' g='192,511,57,44'>"
+        + "      <transition name='to end1' to='end1' g='-42,-18'/>"
+        + "   </join>"
+        + "   <end g='193,606,38,33' name='end1'/>"
+        + "</process>");
+
+      Map<String, ?> variables = Collections.singletonMap("actors", Arrays.asList("alex", "mike"));
+      !
 ProcessInstance processInstance = executionService.startProces!
 sInstanc
eByKey("ForEachConditionFalse", variables);
+
+      fail("expected foreach all conditions evaluated to false");
+    }
+    catch (JbpmException e) {
+      // expected result
+    }
+  }
+}


More information about the jbpm-commits mailing list