### Eclipse Workspace Patch 1.0
#P jbpm4
Index: modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/ForEachForkTest.java
===================================================================
--- modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/ForEachForkTest.java (revision 0)
+++ modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/ForEachForkTest.java (revision 0)
@@ -0,0 +1,317 @@
+package org.jbpm.test.activity.forkjoin;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import org.jbpm.api.Execution;
+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 ForEachForkTest extends JbpmTestCase {
+
+ public void testForEachForkWithOneTransition() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork");
+
+ Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult();
+ assertNotNull(taskAlex);
+ assertEquals("task1", taskAlex.getActivityName());
+
+ taskService.completeTask(taskAlex.getId());
+
+ Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult();
+ assertNotNull(taskMike);
+ 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();
+
+ assertNotNull(history);
+ assertEquals(ProcessInstance.STATE_ENDED, history.getState());
+ assertEquals("end1", history.getEndActivityName());
+ }
+
+ public void testForEachForkWithTwoTransitions() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ +" "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork");
+ List tasksAlex = taskService.createTaskQuery().assignee("alex").list();
+ assertNotNull(tasksAlex);
+ assertEquals(2, tasksAlex.size());
+
+ List tasksMike = taskService.createTaskQuery().assignee("mike").list();
+ assertNotNull(tasksMike);
+ assertEquals(2, tasksMike.size());
+
+ assertEquals("task1", tasksAlex.get(0).getActivityName());
+ assertEquals("task2", tasksAlex.get(1).getActivityName());
+ // complete Alex's tasks
+ for (Task taskAlex : tasksAlex) {
+
+ taskService.completeTask(taskAlex.getId());
+
+ }
+
+ assertEquals("task1", tasksMike.get(0).getActivityName());
+ assertEquals("task2", tasksMike.get(1).getActivityName());
+ // complete Mike's tasks
+ for (Task taskMike : tasksMike) {
+
+ taskService.completeTask(taskMike.getId());
+
+ }
+
+ HistoryProcessInstance history = historyService
+ .createHistoryProcessInstanceQuery()
+ .processInstanceId(processInstance.getId())
+ .uniqueResult();
+
+ assertNotNull(history);
+ assertEquals(ProcessInstance.STATE_ENDED, history.getState());
+ assertEquals("end1", history.getEndActivityName());
+ }
+
+ public void testForEachForkWithOneTransitionAndExpressionList() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+ List assignees = new ArrayList();
+ assignees.add("mike");
+ assignees.add("alex");
+
+ HashMap props = new HashMap();
+ props.put("foreachItems", assignees);
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props);
+ Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult();
+ assertNotNull(taskAlex);
+ assertEquals("task1", taskAlex.getActivityName());
+
+ Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult();
+ assertNotNull(taskMike);
+ assertEquals("task1", taskMike.getActivityName());
+
+ taskService.completeTask(taskAlex.getId());
+
+ 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();
+
+ assertNotNull(history);
+ assertEquals(ProcessInstance.STATE_ENDED, history.getState());
+ assertEquals("end1", history.getEndActivityName());
+ }
+
+ public void testForEachForkWithOneTransitionAndExpressionArray() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+
+ HashMap props = new HashMap();
+ props.put("foreachItems", new String[]{"alex", "mike"});
+
+ ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props);
+ Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult();
+ assertNotNull(taskAlex);
+ assertEquals("task1", taskAlex.getActivityName());
+
+ Task taskMike = taskService.createTaskQuery().assignee("mike").uniqueResult();
+ assertNotNull(taskMike);
+ assertEquals("task1", taskMike.getActivityName());
+
+ taskService.completeTask(taskAlex.getId());
+
+ 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();
+
+ assertNotNull(history);
+ assertEquals(ProcessInstance.STATE_ENDED, history.getState());
+ assertEquals("end1", history.getEndActivityName());
+ }
+
+ public void testForEachForkNotSupportedExpression() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+
+ HashMap props = new HashMap();
+ props.put("foreachItems", new Date());
+ try {
+ executionService.startProcessInstanceByKey("ForEachFork", props);
+
+ fail("It should fail, since for-each list of items is a Date object");
+ } catch (Exception e) {
+ // expected result
+ }
+ }
+
+ public void testForEachForkMissingAttribute() {
+ deployJpdlXmlString(""
+ + ""
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + " "
+ + "");
+
+
+ HashMap props = new HashMap();
+ props.put("foreachItems", new Date());
+ try {
+ executionService.startProcessInstanceByKey("ForEachFork", props);
+
+ fail("It should fail, because both for-each and var attributes must be given");
+ } catch (Exception e) {
+ // expected result
+ }
+ }
+}
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 6285)
+++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkActivity.java (working copy)
@@ -22,14 +22,18 @@
package org.jbpm.jpdl.internal.activity;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jbpm.api.Execution;
+import org.jbpm.api.JbpmException;
import org.jbpm.api.activity.ActivityExecution;
import org.jbpm.api.model.Activity;
import org.jbpm.api.model.Transition;
+import org.jbpm.pvm.internal.el.Expression;
import org.jbpm.pvm.internal.model.Condition;
import org.jbpm.pvm.internal.model.ExecutionImpl;
import org.jbpm.pvm.internal.model.TransitionImpl;
@@ -37,17 +41,25 @@
/**
* @author Tom Baeyens
+ * @author Maciej Swiderski
*/
public class ForkActivity extends JpdlActivity {
private static final long serialVersionUID = 1L;
+ private String forEachVariable;
+
+ private Expression forEachExpression;
+
+
public void execute(ActivityExecution execution) {
execute((ExecutionImpl)execution);
}
public void execute(ExecutionImpl execution) {
Activity activity = execution.getActivity();
+ //prepare for for-each
+ Collection> forEachItems = resolveCollection(execution);
// evaluate the conditions and find the transitions that should be forked
List forkingTransitions = new ArrayList();
@@ -68,30 +80,50 @@
// if there is exactly 1 transition to be taken, just use the incoming execution
} else if (forkingTransitions.size()==1) {
- execution.take(forkingTransitions.get(0));
+
+ if( forEachItems == null || forEachItems.isEmpty()) {
+ execution.take(forkingTransitions.get(0));
+ } else {
+ // perform for each
+ ExecutionImpl concurrentRoot = buildConcurrentRoot(execution);
+
+ for (Object o : forEachItems) {
+
+ ExecutionImpl concurrentExecution = buildChildExecution(forkingTransitions.get(0), concurrentRoot, activity);
+ ExecutionImpl parent = concurrentExecution.getParent();
+ concurrentExecution.setParent(null);
+ concurrentExecution.setVariable(this.forEachVariable, o);
+ concurrentExecution.setParent(parent);
+ concurrentExecution.take(forkingTransitions.get(0));
+ }
+
+ }
// if there are more transitions
} else {
- ExecutionImpl concurrentRoot = null;
- 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();
- }
+ ExecutionImpl concurrentRoot = buildConcurrentRoot(execution);
Map childExecutionsMap = new HashMap();
for (Transition transition : forkingTransitions) {
// launch a concurrent path of execution
- String childExecutionName = transition.getName();
- ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
- concurrentExecution.setActivity(activity);
- concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
- childExecutionsMap.put(transition, concurrentExecution);
+ if( forEachItems == null || forEachItems.isEmpty()) {
+ ExecutionImpl concurrentExecution = buildChildExecution(transition, concurrentRoot, activity);
+ childExecutionsMap.put(transition, concurrentExecution);
+ } else {
+
+ // perform for each
+ for (Object o : forEachItems) {
+
+ ExecutionImpl concurrentExecution = buildChildExecution(transition, concurrentRoot, activity);
+ ExecutionImpl parent = concurrentExecution.getParent();
+ concurrentExecution.setParent(null);
+ concurrentExecution.setVariable(this.forEachVariable, o);
+ concurrentExecution.setParent(parent);
+ concurrentExecution.take(transition);
+ }
+ }
}
-
+ // this will be used only when for-each is not used
for (Transition transition : childExecutionsMap.keySet()) {
childExecutionsMap.get(transition).take(transition);
@@ -101,4 +133,77 @@
}
}
}
+
+ private ExecutionImpl buildChildExecution(Transition transition, ExecutionImpl concurrentRoot, Activity activity) {
+ String childExecutionName = transition.getName();
+ ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
+ concurrentExecution.setActivity(activity);
+ concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
+
+ return concurrentExecution;
+ }
+
+ private ExecutionImpl buildConcurrentRoot(ExecutionImpl execution) {
+
+ ExecutionImpl concurrentRoot = null;
+ 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();
+ }
+
+ return concurrentRoot;
+ }
+
+ private Collection> resolveCollection(ExecutionImpl execution) {
+
+ // for each is not active
+ if (this.forEachExpression == null) {
+ return null;
+ }
+
+ Object expressionValue = this.forEachExpression.evaluate(execution);
+ // check if evaluation result is null to prevent NPE later on
+ if (expressionValue == null) {
+ return null;
+ }
+
+ // check of supported objects as for each list
+ if (expressionValue instanceof String) {
+ // first regular strings that could be represented as comma separated list
+ String[] forEachElements = ((String)expressionValue).split(",");
+ return Arrays.asList(forEachElements);
+ } else if (expressionValue instanceof Collection>) {
+ // anything that implements collection
+ return (Collection< ? >) expressionValue;
+ } else if (expressionValue.getClass().isArray()) {
+ // any kind of array that will be returned as a list
+ return Arrays.asList((Object[])expressionValue);
+ } else {
+ throw new JbpmException("Unsupported type (" + expressionValue.getClass().getName() + ") of items given as for-each attribute");
+ }
+
+ }
+
+
+ public String getForEachVariable() {
+ return forEachVariable;
+ }
+
+
+ public void setForEachVariable(String forEachVariable) {
+ this.forEachVariable = forEachVariable;
+ }
+
+ public Expression getForEachExpression() {
+ return forEachExpression;
+ }
+
+
+ public void setForEachExpression(Expression forEachExpression) {
+ this.forEachExpression = forEachExpression;
+ }
}
Index: modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkBinding.java
===================================================================
--- modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkBinding.java (revision 6285)
+++ modules/jpdl/src/main/java/org/jbpm/jpdl/internal/activity/ForkBinding.java (working copy)
@@ -22,6 +22,7 @@
package org.jbpm.jpdl.internal.activity;
import org.jbpm.jpdl.internal.xml.JpdlParser;
+import org.jbpm.pvm.internal.el.Expression;
import org.jbpm.pvm.internal.xml.Parse;
import org.w3c.dom.Element;
@@ -30,13 +31,34 @@
* @author Tom Baeyens
*/
public class ForkBinding extends JpdlBinding {
+
+ private final static String FOR_EACH = "for-each";
+ private final static String FOR_EACH_VAR = "var";
public ForkBinding() {
super("fork");
}
public Object parseJpdl(Element element, Parse parse, JpdlParser parser) {
- return new ForkActivity();
+ ForkActivity forkActivity = new ForkActivity();
+ if (element.hasAttribute(FOR_EACH)) {
+ String forEachText = element.getAttribute(FOR_EACH);
+
+ Expression expression = Expression.create(forEachText, Expression.LANGUAGE_UEL_VALUE);
+ forkActivity.setForEachExpression(expression);
+ }
+
+ if (element.hasAttribute(FOR_EACH_VAR)) {
+ String forEachVar = element.getAttribute(FOR_EACH_VAR);
+
+ forkActivity.setForEachVariable(forEachVar);
+ }
+ /*
+ * Enable this to perform validation when for-each will be included in XSD
+ if ((element.hasAttribute(FOR_EACH) && !element.hasAttribute(FOR_EACH_VAR)) || (!element.hasAttribute(FOR_EACH) && element.hasAttribute(FOR_EACH_VAR))) {
+ parse.addProblem("Both for-each and var attributes must be given to use for-each functionality", element);
+ }*/
+ return forkActivity;
}
}