### 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,548 @@ +/* + * 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.forkjoin; + + +import java.util.HashMap; + +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 + } + } + + public void testForEachForkWithOneTransitionMultiplicityExpression() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + + HashMap props = new HashMap(); + props.put("waitUntil", 2); + + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props); + + 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 testForEachForkWrongMultiplicityExpression() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + + HashMap props = new HashMap(); + props.put("waitUntil", "test"); + try { + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props); + + 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()); + } + + fail("It should fail, because multiplicity attribute of join activity is not a number"); + } catch (Exception e) { + // expected result + } + } + + public void testForEachForkWithOneTransitionMultiplicityExpressionWaitForOneOnly() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + + HashMap props = new HashMap(); + props.put("waitUntil", 1); + + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertNotNull(taskAlex); + 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(); + + assertNotNull(history); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + }*/ + + public void testForEachForkWithOneTransitionMultiplicityExpressionWaitForOneOnlyExt() { + deployJpdlXmlString("" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""); + + + HashMap props = new HashMap(); + props.put("waitUntil", "1"); + + ProcessInstance processInstance = executionService.startProcessInstanceByKey("ForEachFork", props); + + Task taskAlex = taskService.createTaskQuery().assignee("alex").uniqueResult(); + assertNotNull(taskAlex); + assertEquals("task1", taskAlex.getActivityName()); + + taskService.completeTask(taskAlex.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + + assertEquals(2, processInstance.getExecutions().size()); + Execution exec = processInstance.getExecutions().iterator().next(); + executionService.signalExecutionById(exec.getId()); + + processInstance = executionService.findProcessInstanceById(processInstance.getId()); + // this should be already only one active execution + assertEquals(1, processInstance.findActiveActivityNames().size()); + + executionService.signalExecutionById(processInstance.getId()); + + HistoryProcessInstance history = historyService.createHistoryProcessInstanceQuery().processInstanceId(processInstance.getId()).uniqueResult(); + + assertNotNull(history); + assertEquals(ProcessInstance.STATE_ENDED, history.getState()); + assertEquals("end1", history.getEndActivityName()); + } +} 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; + String forEachVariable; + + 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,67 @@ } } } + + 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 void setForEachVariable(String forEachVariable) { + this.forEachVariable = forEachVariable; + } + + 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; } }