Skip to content

Commit

Permalink
Fix issue with multi instance tasks with change state: #3944
Browse files Browse the repository at this point in the history
  • Loading branch information
tijsrademakers committed Dec 2, 2024
1 parent b9d05d1 commit 219ecb9
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ public List<MoveExecutionEntityContainer> 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());
}
Expand All @@ -150,6 +154,7 @@ public List<MoveExecutionEntityContainer> resolveMoveExecutionEntityContainers(C
if (executionContainer.getNewOwnerId() != null) {
moveExecutionEntityContainer.setNewOwnerId(executionContainer.getNewOwnerId());
}

moveExecutionEntityContainerList.add(moveExecutionEntityContainer);
});

Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, ExecutionEntity> continueParentExecutionMap = new HashMap<>();
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list();
assertThat(executions).hasSize(4);
List<Task> 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<String, Object> 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definition"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
targetNamespace="Examples">

<process id="parallelMultiInstance">

<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="beforeMultiInstance"/>
<userTask id="beforeMultiInstance"/>
<sequenceFlow sourceRef="beforeMultiInstance" targetRef="parallelTasks1"/>

<userTask id="parallelTasks1" name="Parallel Task 1">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>${nrOfLoops}</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>

<sequenceFlow id="flow2" sourceRef="parallelTasks1" targetRef="parallelTasks2"/>

<userTask id="parallelTasks2" name="Parallel Task 2">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>${nrOfLoops}</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>

<sequenceFlow id="flow3" sourceRef="parallelTasks2" targetRef="nextTask"/>
<userTask id="nextTask" name="next task"/>
<sequenceFlow id="flow4" sourceRef="nextTask" targetRef="theEnd"/>
<endEvent id="theEnd"/>

</process>

</definitions>

0 comments on commit 219ecb9

Please sign in to comment.