diff --git a/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/constants/BpmnXMLConstants.java b/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/constants/BpmnXMLConstants.java index 295f0a9d92c427ba8813bd2f3362eb4ddd46bd4c..709c02e87ea613c1a4a0590c6016b844ee1d77b0 100644 --- a/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/constants/BpmnXMLConstants.java +++ b/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/constants/BpmnXMLConstants.java @@ -133,7 +133,8 @@ public interface BpmnXMLConstants { public static final String ATTRIBUTE_MULTIINSTANCE_SEQUENTIAL = "isSequential"; public static final String ATTRIBUTE_MULTIINSTANCE_COLLECTION = "collection"; public static final String ATTRIBUTE_MULTIINSTANCE_VARIABLE = "elementVariable"; - + public static final String ATTRIBUTE_MULTIINSTANCE_INDEX_VARIABLE = "elementIndexVariable"; + public static final String ATTRIBUTE_TASK_IMPLEMENTATION = "implementation"; public static final String ATTRIBUTE_TASK_OPERATION_REF = "operationRef"; diff --git a/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/converter/child/MultiInstanceParser.java b/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/converter/child/MultiInstanceParser.java index f75d16eca8476d37617eab5163a6324b14e59761..5e0b3c1fd7f4cbf0f7dae410e3a061e270700d95 100644 --- a/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/converter/child/MultiInstanceParser.java +++ b/modules/activiti-bpmn-converter/src/main/java/org/activiti/bpmn/converter/child/MultiInstanceParser.java @@ -39,6 +39,7 @@ public class MultiInstanceParser extends BaseChildElementParser { } multiInstanceDef.setInputDataItem(xtr.getAttributeValue(ACTIVITI_EXTENSIONS_NAMESPACE, ATTRIBUTE_MULTIINSTANCE_COLLECTION)); multiInstanceDef.setElementVariable(xtr.getAttributeValue(ACTIVITI_EXTENSIONS_NAMESPACE, ATTRIBUTE_MULTIINSTANCE_VARIABLE)); + multiInstanceDef.setElementIndexVariable(xtr.getAttributeValue(ACTIVITI_EXTENSIONS_NAMESPACE, ATTRIBUTE_MULTIINSTANCE_INDEX_VARIABLE)); boolean readyWithMultiInstance = false; try { diff --git a/modules/activiti-bpmn-model/src/main/java/org/activiti/bpmn/model/MultiInstanceLoopCharacteristics.java b/modules/activiti-bpmn-model/src/main/java/org/activiti/bpmn/model/MultiInstanceLoopCharacteristics.java index 5ee0d16ed4dd114e98c7f348522c383402ade2ff..56c27741aa97c188391dfe3c840daf8c195b45fc 100644 --- a/modules/activiti-bpmn-model/src/main/java/org/activiti/bpmn/model/MultiInstanceLoopCharacteristics.java +++ b/modules/activiti-bpmn-model/src/main/java/org/activiti/bpmn/model/MultiInstanceLoopCharacteristics.java @@ -21,6 +21,7 @@ public class MultiInstanceLoopCharacteristics extends BaseElement { protected String loopCardinality; protected String completionCondition; protected String elementVariable; + protected String elementIndexVariable; protected boolean sequential; public String getInputDataItem() { @@ -47,6 +48,12 @@ public class MultiInstanceLoopCharacteristics extends BaseElement { public void setElementVariable(String elementVariable) { this.elementVariable = elementVariable; } + public String getElementIndexVariable() { + return elementIndexVariable; + } + public void setElementIndexVariable(String elementIndexVariable) { + this.elementIndexVariable = elementIndexVariable; + } public boolean isSequential() { return sequential; } diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java index d632753b8d667cdf70f287792669b6e910cbed64..c6110105ac3615c425eeff14d948f32b17e4cbc0 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java @@ -60,9 +60,6 @@ public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBeha protected final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances"; protected final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances"; - // Variable names for inner instances (as described in the spec) - protected final String LOOP_COUNTER = "loopCounter"; - // Instance members protected ActivityImpl activity; protected AbstractBpmnActivityBehavior innerActivityBehavior; @@ -71,7 +68,9 @@ public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBeha protected Expression collectionExpression; protected String collectionVariable; protected String collectionElementVariable; - + // default variable name for loop counter for inner instances (as described in the spec) + protected String collectionElementIndexVariable="loopCounter"; + /** * @param innerActivityBehavior The original {@link ActivityBehavior} of the activity * that will be wrapped inside this behavior. @@ -84,7 +83,7 @@ public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBeha } public void execute(ActivityExecution execution) throws Exception { - if (getLocalLoopVariable(execution, LOOP_COUNTER) == null) { + if (getLocalLoopVariable(execution, getCollectionElementIndexVariable()) == null) { try { createInstances(execution); } catch (BpmnError error) { @@ -291,6 +290,12 @@ public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBeha public void setCollectionElementVariable(String collectionElementVariable) { this.collectionElementVariable = collectionElementVariable; } + public String getCollectionElementIndexVariable() { + return collectionElementIndexVariable; + } + public void setCollectionElementIndexVariable(String collectionElementIndexVariable) { + this.collectionElementIndexVariable = collectionElementIndexVariable; + } public void setInnerActivityBehavior(AbstractBpmnActivityBehavior innerActivityBehavior) { this.innerActivityBehavior = innerActivityBehavior; this.innerActivityBehavior.setMultiInstanceActivityBehavior(this); diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/ParallelMultiInstanceBehavior.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/ParallelMultiInstanceBehavior.java index 82daa3d98a0f3e0717855f2c82397c47ac9c5eee..67264170b19875ead3bf2de8de9a4c3d7385e2a0 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/ParallelMultiInstanceBehavior.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/ParallelMultiInstanceBehavior.java @@ -78,7 +78,7 @@ public class ParallelMultiInstanceBehavior extends MultiInstanceActivityBehavior if (concurrentExecution.isActive() && !concurrentExecution.isEnded() && concurrentExecution.getParent().isActive() && !concurrentExecution.getParent().isEnded()) { - setLoopVariable(concurrentExecution, LOOP_COUNTER, loopCounter); + setLoopVariable(concurrentExecution, getCollectionElementIndexVariable(), loopCounter); executeOriginalBehavior(concurrentExecution, loopCounter); } } @@ -101,7 +101,7 @@ public class ParallelMultiInstanceBehavior extends MultiInstanceActivityBehavior public void leave(ActivityExecution execution) { callActivityEndListeners(execution); - int loopCounter = getLoopVariable(execution, LOOP_COUNTER); + int loopCounter = getLoopVariable(execution, getCollectionElementIndexVariable()); int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES); int nrOfCompletedInstances = getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES) + 1; int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES) - 1; diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/SequentialMultiInstanceBehavior.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/SequentialMultiInstanceBehavior.java index 00055af342c45d80a39285b81067e8cd47a972b4..b171525b112e5c4cebabd0d7e72a1bd7b78b9acd 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/SequentialMultiInstanceBehavior.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/SequentialMultiInstanceBehavior.java @@ -43,7 +43,7 @@ public class SequentialMultiInstanceBehavior extends MultiInstanceActivityBehavi setLoopVariable(execution, NUMBER_OF_INSTANCES, nrOfInstances); setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, 0); - setLoopVariable(execution, LOOP_COUNTER, 0); + setLoopVariable(execution, getCollectionElementIndexVariable(), 0); setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, 1); logLoopDetails(execution, "initialized", 0, 0, 1, nrOfInstances); @@ -58,12 +58,12 @@ public class SequentialMultiInstanceBehavior extends MultiInstanceActivityBehavi public void leave(ActivityExecution execution) { callActivityEndListeners(execution); - int loopCounter = getLoopVariable(execution, LOOP_COUNTER) + 1; + int loopCounter = getLoopVariable(execution, getCollectionElementIndexVariable()) + 1; int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES); int nrOfCompletedInstances = getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES) + 1; int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES); - setLoopVariable(execution, LOOP_COUNTER, loopCounter); + setLoopVariable(execution, getCollectionElementIndexVariable(), loopCounter); setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances); logLoopDetails(execution, "instance completed", loopCounter, nrOfCompletedInstances, nrOfActiveInstances, nrOfInstances); diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/AbstractActivityBpmnParseHandler.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/AbstractActivityBpmnParseHandler.java index c36960169813eb3fa4c89a0d6272944be2c1cae2..23e461b5c6a55dd1f4f910d8376f03dbb571a698 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/AbstractActivityBpmnParseHandler.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/AbstractActivityBpmnParseHandler.java @@ -91,6 +91,11 @@ public abstract class AbstractActivityBpmnParseHandler exten miActivityBehavior.setCollectionElementVariable(loopCharacteristics.getElementVariable()); } + // activiti:elementIndexVariable + if (StringUtils.isNotEmpty(loopCharacteristics.getElementIndexVariable())) { + miActivityBehavior.setCollectionElementIndexVariable(loopCharacteristics.getElementIndexVariable()); + } + // Validation if (miActivityBehavior.getLoopCardinalityExpression() == null && miActivityBehavior.getCollectionExpression() == null && miActivityBehavior.getCollectionVariable() == null) { diff --git a/modules/activiti-engine/src/test/java/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.java b/modules/activiti-engine/src/test/java/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.java index 05ebee048531997d45d393b00d131c165e0b422d..309902de51ff2a368c6c38e1bf1c8ff325a30a92 100644 --- a/modules/activiti-engine/src/test/java/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.java +++ b/modules/activiti-engine/src/test/java/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.java @@ -43,28 +43,37 @@ public class MultiInstanceTest extends PluggableActivitiTestCase { @Deployment(resources = {"org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.sequentialUserTasks.bpmn20.xml"}) public void testSequentialUserTasks() { - String procId = runtimeService.startProcessInstanceByKey("miSequentialUserTasks", + checkSequentialUserTasks("miSequentialUserTasks"); + } + + @Deployment + public void testSequentialUserTasksCustomExtensions() { + checkSequentialUserTasks("miSequentialUserTasksCustomExtensions"); + } + + private void checkSequentialUserTasks(String processDefinitionKey) { + String procId = runtimeService.startProcessInstanceByKey(processDefinitionKey, CollectionUtil.singletonMap("nrOfLoops", 3)).getId(); - + Task task = taskService.createTaskQuery().singleResult(); assertEquals("My Task", task.getName()); assertEquals("kermit_0", task.getAssignee()); taskService.complete(task.getId()); - + task = taskService.createTaskQuery().singleResult(); assertEquals("My Task", task.getName()); assertEquals("kermit_1", task.getAssignee()); taskService.complete(task.getId()); - + task = taskService.createTaskQuery().singleResult(); assertEquals("My Task", task.getName()); assertEquals("kermit_2", task.getAssignee()); taskService.complete(task.getId()); - + assertNull(taskService.createTaskQuery().singleResult()); assertProcessEnded(procId); } - + @Deployment(resources = {"org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.sequentialUserTasks.bpmn20.xml"}) public void testSequentialUserTasksHistory() { runtimeService.startProcessInstanceByKey("miSequentialUserTasks", @@ -240,18 +249,39 @@ public class MultiInstanceTest extends PluggableActivitiTestCase { @Deployment public void testParallelUserTasksCustomExtensions() { + checkParallelUserTasksCustomExtensions("miParallelUserTasks"); + } + + @Deployment + public void testParallelUserTasksCustomExtensionsLoopIndexVariable() { + checkParallelUserTasksCustomExtensions("miParallelUserTasksLoopVariable"); + } + + private void checkParallelUserTasksCustomExtensions(String processDefinitionKey) { Map vars = new HashMap(); List assigneeList = Arrays.asList("kermit", "gonzo", "fozzie"); vars.put("assigneeList", assigneeList); - runtimeService.startProcessInstanceByKey("miSequentialUserTasks", vars); - - for (String assignee : assigneeList) { - Task task = taskService.createTaskQuery().singleResult(); - assertEquals(assignee, task.getAssignee()); - taskService.complete(task.getId()); - } + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, vars); + + List tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(3, tasks.size()); + assertEquals("My Task 0", tasks.get(0).getName()); + assertEquals("My Task 1", tasks.get(1).getName()); + assertEquals("My Task 2", tasks.get(2).getName()); + + tasks = taskService.createTaskQuery().orderByTaskAssignee().asc().list(); + assertEquals("fozzie", tasks.get(0).getAssignee()); + assertEquals("gonzo", tasks.get(1).getAssignee()); + assertEquals("kermit", tasks.get(2).getAssignee()); + + // Completing 3 tasks will trigger completioncondition + taskService.complete(tasks.get(0).getId()); + taskService.complete(tasks.get(1).getId()); + taskService.complete(tasks.get(2).getId()); + assertEquals(0, taskService.createTaskQuery().count()); + assertProcessEnded(processInstance.getProcessInstanceId()); } - + @Deployment public void testParallelUserTasksExecutionAndTaskListeners() { runtimeService.startProcessInstanceByKey("miParallelUserTasks"); diff --git a/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensions.bpmn20.xml b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensions.bpmn20.xml index 9324647a251474208a5d910e2eb907f20ea1db80..3e01b1978f30a35932162cb2812621a1be58e576 100644 --- a/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensions.bpmn20.xml +++ b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensions.bpmn20.xml @@ -5,13 +5,13 @@ xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples"> - + - diff --git a/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensionsLoopIndexVariable.bpmn20.xml b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensionsLoopIndexVariable.bpmn20.xml new file mode 100644 index 0000000000000000000000000000000000000000..e7f85f2e9b789397e033bcab938c46db6b98b084 --- /dev/null +++ b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testParallelUserTasksCustomExtensionsLoopIndexVariable.bpmn20.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialUserTasksCustomExtensions.bpmn20.xml b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialUserTasksCustomExtensions.bpmn20.xml new file mode 100644 index 0000000000000000000000000000000000000000..21c66c549c1604dfe2b918abdc11ce5a1bee42a8 --- /dev/null +++ b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialUserTasksCustomExtensions.bpmn20.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + ${nrOfLoops} + ${nrOfCompletedInstances == 5} + + + + + + + + + \ No newline at end of file diff --git a/userguide/src/en/chapters/ch07b-BPMN-Constructs.xml b/userguide/src/en/chapters/ch07b-BPMN-Constructs.xml index b0da8bf2207ad28fb812e4dff9867bff87e932f4..480eae95f6c719ca62b95b35a06728d3b40c21e5 100644 --- a/userguide/src/en/chapters/ch07b-BPMN-Constructs.xml +++ b/userguide/src/en/chapters/ch07b-BPMN-Constructs.xml @@ -4835,7 +4835,8 @@ public class MyTaskCreateListener implements TaskListener { Additionally, each of the created executions will have an execution-local variable (i.e. not visible for the other executions, and not stored on process instance level) : - loopCounter: indicates the index in the for-each loop of that particular instance. + loopCounter: indicates the index in the for-each loop of that particular instance. loopCounter variable can be renamed by activiti + elementIndexVariable attribute.