diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/agenda/Agenda.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/agenda/Agenda.java index 48773a56d39fb586192aa16d30d40d70c1f03079..e68529bf5f4c8e35cbad6eedf6d52f0434b7466a 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/agenda/Agenda.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/agenda/Agenda.java @@ -15,6 +15,7 @@ package org.activiti.engine.impl.agenda; import java.util.LinkedList; import org.activiti.engine.impl.interceptor.CommandContext; +import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,30 +47,41 @@ public class Agenda { * Generic method to plan a {@link Runnable}. */ public void planOperation(Runnable operation) { + planOperation(operation, null); + } + + /** + * Generic method to plan a {@link Runnable}. + */ + public void planOperation(Runnable operation, ExecutionEntity executionEntity) { operations.add(operation); logger.debug("Operation {} added to agenda", operation.getClass()); + + if (executionEntity != null) { + commandContext.addInvolvedExecution(executionEntity); + } } /* SPECIFIC operations */ public void planContinueProcessOperation(ActivityExecution execution) { - planOperation(new ContinueProcessOperation(this, execution)); + planOperation(new ContinueProcessOperation(this, execution), (ExecutionEntity) execution); } public void planTakeOutgoingSequenceFlowsOperation(ActivityExecution execution, boolean evaluateConditions) { - planOperation(new TakeOutgoingSequenceFlowsOperation(this, execution, evaluateConditions)); + planOperation(new TakeOutgoingSequenceFlowsOperation(this, execution, evaluateConditions), (ExecutionEntity) execution); } public void planEndExecutionOperation(ActivityExecution execution) { - planOperation(new EndExecutionOperation(this, execution)); + planOperation(new EndExecutionOperation(this, execution), (ExecutionEntity) execution); } public void planTriggerExecutionOperation(ActivityExecution execution) { - planOperation(new TriggerExecutionOperation(this, execution)); + planOperation(new TriggerExecutionOperation(this, execution), (ExecutionEntity) execution); } public void planDestroyScopeOperation(ActivityExecution execution) { - planOperation(new DestroyScopeOperation(this, execution)); + planOperation(new DestroyScopeOperation(this, execution), (ExecutionEntity) execution); } public void planExecuteInactiveBehaviorsOperation() { diff --git a/modules/activiti-engine/src/test/java/org/activiti6/Activiti6Tests.java b/modules/activiti-engine/src/test/java/org/activiti6/Activiti6Tests.java index 23e72f6edf0dfb72609c646d690fb0f3527fd532..892d20abfe2b5f0f418acc47d6ff99dfe794c753 100644 --- a/modules/activiti-engine/src/test/java/org/activiti6/Activiti6Tests.java +++ b/modules/activiti-engine/src/test/java/org/activiti6/Activiti6Tests.java @@ -410,5 +410,71 @@ public class Activiti6Tests extends AbstractActvitiTest { } + /** + * Based on the process and use cases described in http://www.bp-3.com/blogs/2013/09/joins-and-ibm-bpm-diving-deeper/ + */ + @Test + @org.activiti.engine.test.Deployment + public void testInclusiveTrickyMerge() { + + // Use case 1 (easy): "When C completes, depending on the data, we can immediately issue E no matter what the status is of A or B." + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("trickyInclusiveMerge"); + assertNotNull(processInstance); + assertFalse(processInstance.isEnded()); + assertEquals(3, taskService.createTaskQuery().count()); + + Task taskC = taskService.createTaskQuery().taskName("C").singleResult(); + taskService.complete(taskC.getId()); + List tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(3, tasks.size()); + assertEquals("A", tasks.get(0).getName()); + assertEquals("B", tasks.get(1).getName()); + assertEquals("E", tasks.get(2).getName()); + + taskService.complete(tasks.get(0).getId()); + taskService.complete(tasks.get(1).getId()); + tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(2, tasks.size()); + assertEquals("D", tasks.get(0).getName()); + assertEquals("E", tasks.get(1).getName()); + + taskService.complete(tasks.get(0).getId()); + taskService.complete(tasks.get(1).getId()); + assertEquals(0, runtimeService.createExecutionQuery().count()); + + + // Use case 2 (tricky): "If A and B are complete and C routes to E, D will be issued in Parallel to E" + // It's tricky cause the inclusive gateway is not visited directly. + // Instead, it's done by the InactivatedActivityBehavior + + processInstance = runtimeService.startProcessInstanceByKey("trickyInclusiveMerge"); + assertEquals(3, taskService.createTaskQuery().count()); + + tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(3, tasks.size()); + assertEquals("A", tasks.get(0).getName()); + assertEquals("B", tasks.get(1).getName()); + assertEquals("C", tasks.get(2).getName()); + taskService.complete(tasks.get(0).getId()); + taskService.complete(tasks.get(1).getId()); + + // C should still be open + tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(1, tasks.size()); + assertEquals("C", tasks.get(0).getName()); + + // If C is now completed, the inclusive gateway should also be completed and D and E should be open tasks + taskService.complete(tasks.get(0).getId()); + tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); + assertEquals(2, tasks.size()); + assertEquals("D", tasks.get(0).getName()); + assertEquals("E", tasks.get(1).getName()); + + // Completing them should just end the process instance + taskService.complete(tasks.get(0).getId()); + taskService.complete(tasks.get(1).getId()); + assertEquals(0, runtimeService.createExecutionQuery().count()); + } + } diff --git a/modules/activiti-engine/src/test/resources/org/activiti6/Activiti6Tests.testInclusiveTrickyMerge.bpmn20.xml b/modules/activiti-engine/src/test/resources/org/activiti6/Activiti6Tests.testInclusiveTrickyMerge.bpmn20.xml new file mode 100644 index 0000000000000000000000000000000000000000..e123500223992411fb1a0e633d70d4c669f3de2a --- /dev/null +++ b/modules/activiti-engine/src/test/resources/org/activiti6/Activiti6Tests.testInclusiveTrickyMerge.bpmn20.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file