Skip to content

Commit 219ecb9

Browse files
Fix issue with multi instance tasks with change state: #3944
1 parent b9d05d1 commit 219ecb9

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ public List<MoveExecutionEntityContainer> resolveMoveExecutionEntityContainers(C
141141

142142
miExecutionsByParent.values().forEach(executions -> {
143143
MoveExecutionEntityContainer moveExecutionEntityContainer = new MoveExecutionEntityContainer(executions, executionContainer.getMoveToActivityIds());
144+
if (executionsByParent.containsKey(executions.get(0).getId())) {
145+
moveExecutionEntityContainer.setMultiInstanceExecutionWithChildExecutions(true);
146+
}
147+
144148
if (executions.get(0).getVariablesLocal() != null && !executions.get(0).getVariablesLocal().isEmpty()) {
145149
moveExecutionEntityContainer.addLocalVariableMap(executions.get(0).getActivityId(), executions.get(0).getVariablesLocal());
146150
}
@@ -150,6 +154,7 @@ public List<MoveExecutionEntityContainer> resolveMoveExecutionEntityContainers(C
150154
if (executionContainer.getNewOwnerId() != null) {
151155
moveExecutionEntityContainer.setNewOwnerId(executionContainer.getNewOwnerId());
152156
}
157+
153158
moveExecutionEntityContainerList.add(moveExecutionEntityContainer);
154159
});
155160

@@ -604,7 +609,9 @@ protected void doMoveExecutionState(ProcessInstanceChangeState processInstanceCh
604609
CommandContextUtil.getAgenda(commandContext).planContinueProcessWithMigrationContextOperation(newChildExecution, migrationContext);
605610

606611
} else {
607-
if (newChildExecution.isMultiInstanceRoot() && (newChildExecution.getCurrentFlowElement() instanceof Task || newChildExecution.getCurrentFlowElement() instanceof CallActivity)) {
612+
if (newChildExecution.isMultiInstanceRoot() && moveExecutionContainer.isMultiInstanceExecutionWithChildExecutions() &&
613+
(newChildExecution.getCurrentFlowElement() instanceof Task || newChildExecution.getCurrentFlowElement() instanceof CallActivity)) {
614+
608615
continue;
609616
}
610617

modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/MoveExecutionEntityContainer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class MoveExecutionEntityContainer {
3939
protected BpmnModel subProcessModel;
4040
protected BpmnModel processModel;
4141
protected ExecutionEntity superExecution;
42+
protected boolean isMultiInstanceExecutionWithChildExecutions;
4243
protected String newAssigneeId;
4344
protected String newOwnerId;
4445
protected Map<String, ExecutionEntity> continueParentExecutionMap = new HashMap<>();
@@ -145,6 +146,14 @@ public ExecutionEntity getSuperExecution() {
145146
return superExecution;
146147
}
147148

149+
public boolean isMultiInstanceExecutionWithChildExecutions() {
150+
return isMultiInstanceExecutionWithChildExecutions;
151+
}
152+
153+
public void setMultiInstanceExecutionWithChildExecutions(boolean isMultiInstanceExecutionWithChildExecutions) {
154+
this.isMultiInstanceExecutionWithChildExecutions = isMultiInstanceExecutionWithChildExecutions;
155+
}
156+
148157
public void setNewAssigneeId(String newAssigneeId) {
149158
this.newAssigneeId = newAssigneeId;
150159
}

modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForMultiInstanceTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,95 @@ public void testSetCurrentParentExecutionOfParallelMultiInstanceTask() {
380380

381381
assertProcessEnded(processInstance.getId());
382382
}
383+
384+
@Test
385+
@Deployment(resources = "org/flowable/engine/test/api/multiInstanceTwoParallelTasks.bpmn20.xml")
386+
public void testSetCurrentActivityToOtherParallelMultiInstanceTask() {
387+
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder().processDefinitionKey("parallelMultiInstance")
388+
.variable("nrOfLoops", 3)
389+
.start();
390+
391+
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
392+
assertThat(task.getTaskDefinitionKey()).isEqualTo("beforeMultiInstance");
393+
taskService.complete(task.getId());
394+
395+
List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list();
396+
assertThat(executions).hasSize(4);
397+
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
398+
assertThat(tasks).hasSize(3);
399+
400+
runtimeService.createChangeActivityStateBuilder()
401+
.processInstanceId(processInstance.getId())
402+
.moveActivityIdTo("parallelTasks1", "parallelTasks2")
403+
.changeState();
404+
405+
// First in the loop
406+
executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list();
407+
// One MI root and 3 parallel Executions
408+
assertThat(executions)
409+
.extracting(Execution::getActivityId)
410+
.containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2", "parallelTasks2");
411+
412+
Execution miRoot = executions.stream().filter(e -> ((ExecutionEntity) e).isMultiInstanceRoot()).findFirst().get();
413+
Map<String, Object> miRootVars = runtimeService.getVariables(miRoot.getId());
414+
assertThat(miRootVars)
415+
.extracting("nrOfActiveInstances", "nrOfCompletedInstances", "nrOfLoops")
416+
.containsExactly(3, 0, 3);
417+
418+
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list();
419+
assertThat(tasks)
420+
.extracting(Task::getTaskDefinitionKey)
421+
.containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2");
422+
assertThat(tasks)
423+
.extracting(taskEntity -> taskService.getVariable(taskEntity.getId(), "loopCounter"))
424+
.isNotNull();
425+
426+
// Complete one execution
427+
taskService.complete(tasks.get(0).getId());
428+
429+
// Confirm new state
430+
executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list();
431+
assertThat(executions)
432+
.extracting(Execution::getActivityId)
433+
.containsExactly("parallelTasks2", "parallelTasks2", "parallelTasks2", "parallelTasks2");
434+
435+
miRoot = executions.stream().filter(e -> ((ExecutionEntity) e).isMultiInstanceRoot()).findFirst().get();
436+
miRootVars = runtimeService.getVariables(miRoot.getId());
437+
assertThat(miRootVars)
438+
.extracting("nrOfActiveInstances", "nrOfCompletedInstances", "nrOfLoops")
439+
.containsExactly(2, 1, 3);
440+
441+
// Two executions are inactive, the completed before and the MI root
442+
assertThat(executions)
443+
.haveExactly(2, new Condition<>((Execution execution) -> !((ExecutionEntity) execution).isActive(), "inactive"));
444+
445+
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list();
446+
assertThat(tasks)
447+
.extracting(Task::getTaskDefinitionKey)
448+
.containsExactly("parallelTasks2", "parallelTasks2");
449+
assertThat(taskService.getVariable(tasks.get(0).getId(), "loopCounter")).isEqualTo(1);
450+
451+
// Complete the rest of the Tasks
452+
tasks.forEach(this::completeTask);
453+
454+
// After the MI
455+
executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list();
456+
assertThat(executions)
457+
.extracting(Execution::getActivityId)
458+
.containsExactly("nextTask");
459+
assertThat((ExecutionEntity) executions.get(0))
460+
.extracting(ExecutionEntity::isMultiInstanceRoot).isEqualTo(false);
461+
462+
task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().singleResult();
463+
assertThat(task)
464+
.extracting(Task::getTaskDefinitionKey)
465+
.isEqualTo("nextTask");
466+
assertThat(taskService.getVariable(task.getId(), "loopCounter")).isNull();
467+
468+
//Complete the process
469+
taskService.complete(task.getId());
470+
assertProcessEnded(processInstance.getId());
471+
}
383472

384473
@Test
385474
@Deployment(resources = "org/flowable/engine/test/api/multiInstanceParallelSubProcess.bpmn20.xml")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions id="definition"
3+
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
4+
targetNamespace="Examples">
5+
6+
<process id="parallelMultiInstance">
7+
8+
<startEvent id="theStart"/>
9+
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="beforeMultiInstance"/>
10+
<userTask id="beforeMultiInstance"/>
11+
<sequenceFlow sourceRef="beforeMultiInstance" targetRef="parallelTasks1"/>
12+
13+
<userTask id="parallelTasks1" name="Parallel Task 1">
14+
<multiInstanceLoopCharacteristics isSequential="false">
15+
<loopCardinality>${nrOfLoops}</loopCardinality>
16+
</multiInstanceLoopCharacteristics>
17+
</userTask>
18+
19+
<sequenceFlow id="flow2" sourceRef="parallelTasks1" targetRef="parallelTasks2"/>
20+
21+
<userTask id="parallelTasks2" name="Parallel Task 2">
22+
<multiInstanceLoopCharacteristics isSequential="false">
23+
<loopCardinality>${nrOfLoops}</loopCardinality>
24+
</multiInstanceLoopCharacteristics>
25+
</userTask>
26+
27+
<sequenceFlow id="flow3" sourceRef="parallelTasks2" targetRef="nextTask"/>
28+
<userTask id="nextTask" name="next task"/>
29+
<sequenceFlow id="flow4" sourceRef="nextTask" targetRef="theEnd"/>
30+
<endEvent id="theEnd"/>
31+
32+
</process>
33+
34+
</definitions>

0 commit comments

Comments
 (0)