diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java index 45af84988df..c215804c08f 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java @@ -141,6 +141,10 @@ public List resolveMoveExecutionEntityContainers(C miExecutionsByParent.values().forEach(executions -> { MoveExecutionEntityContainer moveExecutionEntityContainer = new MoveExecutionEntityContainer(executions, executionContainer.getMoveToActivityIds()); + if (executionsByParent.containsKey(executions.get(0).getId())) { + moveExecutionEntityContainer.setMultiInstanceExecutionWithChildExecutions(true); + } + if (executions.get(0).getVariablesLocal() != null && !executions.get(0).getVariablesLocal().isEmpty()) { moveExecutionEntityContainer.addLocalVariableMap(executions.get(0).getActivityId(), executions.get(0).getVariablesLocal()); } @@ -150,6 +154,7 @@ public List resolveMoveExecutionEntityContainers(C if (executionContainer.getNewOwnerId() != null) { moveExecutionEntityContainer.setNewOwnerId(executionContainer.getNewOwnerId()); } + moveExecutionEntityContainerList.add(moveExecutionEntityContainer); }); @@ -604,7 +609,9 @@ protected void doMoveExecutionState(ProcessInstanceChangeState processInstanceCh CommandContextUtil.getAgenda(commandContext).planContinueProcessWithMigrationContextOperation(newChildExecution, migrationContext); } else { - if (newChildExecution.isMultiInstanceRoot() && (newChildExecution.getCurrentFlowElement() instanceof Task || newChildExecution.getCurrentFlowElement() instanceof CallActivity)) { + if (newChildExecution.isMultiInstanceRoot() && moveExecutionContainer.isMultiInstanceExecutionWithChildExecutions() && + (newChildExecution.getCurrentFlowElement() instanceof Task || newChildExecution.getCurrentFlowElement() instanceof CallActivity)) { + continue; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/MoveExecutionEntityContainer.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/MoveExecutionEntityContainer.java index 66e0eb97aaa..c8704e443b8 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/MoveExecutionEntityContainer.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/MoveExecutionEntityContainer.java @@ -39,6 +39,7 @@ public class MoveExecutionEntityContainer { protected BpmnModel subProcessModel; protected BpmnModel processModel; protected ExecutionEntity superExecution; + protected boolean isMultiInstanceExecutionWithChildExecutions; protected String newAssigneeId; protected String newOwnerId; protected Map continueParentExecutionMap = new HashMap<>(); @@ -145,6 +146,14 @@ public ExecutionEntity getSuperExecution() { return superExecution; } + public boolean isMultiInstanceExecutionWithChildExecutions() { + return isMultiInstanceExecutionWithChildExecutions; + } + + public void setMultiInstanceExecutionWithChildExecutions(boolean isMultiInstanceExecutionWithChildExecutions) { + this.isMultiInstanceExecutionWithChildExecutions = isMultiInstanceExecutionWithChildExecutions; + } + public void setNewAssigneeId(String newAssigneeId) { this.newAssigneeId = newAssigneeId; } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForMultiInstanceTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForMultiInstanceTest.java index df1dac06fb9..7e20484967b 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForMultiInstanceTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForMultiInstanceTest.java @@ -380,6 +380,95 @@ public void testSetCurrentParentExecutionOfParallelMultiInstanceTask() { assertProcessEnded(processInstance.getId()); } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/multiInstanceTwoParallelTasks.bpmn20.xml") + public void testSetCurrentActivityToOtherParallelMultiInstanceTask() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder().processDefinitionKey("parallelMultiInstance") + .variable("nrOfLoops", 3) + .start(); + + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + assertThat(task.getTaskDefinitionKey()).isEqualTo("beforeMultiInstance"); + taskService.complete(task.getId()); + + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(4); + List tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list(); + assertThat(tasks).hasSize(3); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .moveActivityIdTo("parallelTasks1", "parallelTasks2") + .changeState(); + + // First in the loop + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + // One MI root and 3 parallel Executions + assertThat(executions) + .extracting(Execution::getActivityId) + .containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2", "parallelTasks2"); + + Execution miRoot = executions.stream().filter(e -> ((ExecutionEntity) e).isMultiInstanceRoot()).findFirst().get(); + Map miRootVars = runtimeService.getVariables(miRoot.getId()); + assertThat(miRootVars) + .extracting("nrOfActiveInstances", "nrOfCompletedInstances", "nrOfLoops") + .containsExactly(3, 0, 3); + + tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list(); + assertThat(tasks) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2"); + assertThat(tasks) + .extracting(taskEntity -> taskService.getVariable(taskEntity.getId(), "loopCounter")) + .isNotNull(); + + // Complete one execution + taskService.complete(tasks.get(0).getId()); + + // Confirm new state + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions) + .extracting(Execution::getActivityId) + .containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2", "parallelTasks2"); + + miRoot = executions.stream().filter(e -> ((ExecutionEntity) e).isMultiInstanceRoot()).findFirst().get(); + miRootVars = runtimeService.getVariables(miRoot.getId()); + assertThat(miRootVars) + .extracting("nrOfActiveInstances", "nrOfCompletedInstances", "nrOfLoops") + .containsExactly(2, 1, 3); + + // Two executions are inactive, the completed before and the MI root + assertThat(executions) + .haveExactly(2, new Condition<>((Execution execution) -> !((ExecutionEntity) execution).isActive(), "inactive")); + + tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list(); + assertThat(tasks) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("parallelTasks2", "parallelTasks2"); + assertThat(taskService.getVariable(tasks.get(0).getId(), "loopCounter")).isEqualTo(1); + + // Complete the rest of the Tasks + tasks.forEach(this::completeTask); + + // After the MI + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions) + .extracting(Execution::getActivityId) + .containsExactly("nextTask"); + assertThat((ExecutionEntity) executions.get(0)) + .extracting(ExecutionEntity::isMultiInstanceRoot).isEqualTo(false); + + task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().singleResult(); + assertThat(task) + .extracting(Task::getTaskDefinitionKey) + .isEqualTo("nextTask"); + assertThat(taskService.getVariable(task.getId(), "loopCounter")).isNull(); + + //Complete the process + taskService.complete(task.getId()); + assertProcessEnded(processInstance.getId()); + } @Test @Deployment(resources = "org/flowable/engine/test/api/multiInstanceParallelSubProcess.bpmn20.xml") diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/multiInstanceTwoParallelTasks.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/multiInstanceTwoParallelTasks.bpmn20.xml new file mode 100644 index 00000000000..1b2592cf9b6 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/multiInstanceTwoParallelTasks.bpmn20.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + ${nrOfLoops} + + + + + + + + ${nrOfLoops} + + + + + + + + + + + \ No newline at end of file