Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MNT-24219: local variables before calling subprocess remains after en… #4596

Open
wants to merge 4 commits into
base: 7.11.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.helper.ScopeUtil;
import org.activiti.engine.impl.bpmn.helper.SubProcessVariableSnapshotter;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.delegate.ActivityBehavior;
import org.activiti.engine.impl.delegate.SubProcessActivityBehavior;
Expand All @@ -44,6 +45,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
* This operations ends an execution and follows the typical BPMN rules to continue the process (if possible).
Expand Down Expand Up @@ -220,6 +222,25 @@ protected void handleRegularExecution() {
}
}

private List<ExecutionEntity> getExecutionsBeforeCallingSubProcess(final ExecutionEntity parentExecution, final ExecutionEntity executionToContinue) {
// since parent process execution before calling the subprocess is marked for deletion,
// {@code executionEntityManager.findChildExecutionsByParentExecutionId()} will not return that execution
return parentExecution.getParent().getExecutions().stream()
.filter(executionEntity -> {
// scoped execution of the current subprocess should not be considered
return !executionEntity.getId().equals(parentExecution.getId());
})
.filter(executionEntity -> {
// only executions of the current subProcess should be considered
return executionEntity.getActivityId().equals(parentExecution.getActivityId());
})
.filter(executionEntity -> {
// execution-to-continue should not be considered (we want the execution before calling the subprocess)
return !executionEntity.getId().equals(executionToContinue.getId());
})
.collect(Collectors.toList());
}

protected ExecutionEntity handleSubProcessEnd(ExecutionEntityManager executionEntityManager,
ExecutionEntity parentExecution,
SubProcess subProcess) {
Expand All @@ -229,6 +250,13 @@ protected ExecutionEntity handleSubProcessEnd(ExecutionEntityManager executionEn
executionToContinue = executionEntityManager.createChildExecution(parentExecution.getParent());
executionToContinue.setCurrentFlowElement(subProcess);

// if there's a parent process running (given by parentExecution.getParent()),
// copies local variables from the execution before subprocess
List<ExecutionEntity> executionsBeforeCallingSubProcess = getExecutionsBeforeCallingSubProcess(parentExecution, executionToContinue);
if( executionsBeforeCallingSubProcess.size() == 1 ) {
new SubProcessVariableSnapshotter().setVariablesSnapshots(executionsBeforeCallingSubProcess.get(0), executionToContinue);
}

boolean hasCompensation = false;
if (subProcess instanceof Transaction) {
hasCompensation = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2020 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package org.activiti.engine.test.bpmn.subprocess;

import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.engine.test.Deployment;

import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
* The goal of these tests is to guarantee localVars defined on parentProcess aren't lost
* when calling an embedded subProcess
*/
public class LocalVariablesWithSubProcessTest extends PluggableActivitiTestCase {

@Deployment
public void testLocalVariablesAreAvailableAfterSubProcess() {
// GIVEN a process that creates a local variable, calls a subprocess and evaluates the initial local variable

// WHEN process starts
runtimeService.startProcessInstanceByKey("simplerProcess");

// THEN after completing the subprocess, evaluation of the local variable is possible and flow reaches a user task
TaskQuery taskQuery = taskService.createTaskQuery();
Task taskBeforeSubProcess = taskQuery.singleResult();
assertThat(taskBeforeSubProcess.getName()).isEqualTo("user task");
}

@Deployment
public void testLocalVariablesAreAvailableAfterSubProcessParallelGateway() {
// GIVEN a process with two parallel flows
// GIVEN each flow sets local variables, calls a subprocess and ends with a user task

// WHEN process starts
runtimeService.startProcessInstanceByKey("simplerProcess");

// THEN after completing subprocesses for each flow and reaching user tasks
TaskQuery taskQuery = taskService.createTaskQuery();
List<Task> tasks = taskQuery.list();
assertThat(tasks).hasSize(2);

// THEN when reaching userTaskA (flow A), we have 2 local variables
Task taskA = tasks.stream().filter(task1 -> task1.getName().equals("user task A")).findFirst().orElseThrow();
Map<String, Object> variablesLocalA = runtimeService.getVariablesLocal(taskA.getExecutionId());
assertThat(variablesLocalA).hasSize(2);
assertThat(variablesLocalA.get("commonLocalVar")).isEqualTo("A1");
assertThat(variablesLocalA.get("uniqueLocalVarA")).isEqualTo("A2");

// THEN when reaching userTaskB (flow B), we have 2 local variables
Task taskB = tasks.stream().filter(task1 -> task1.getName().equals("user task B")).findFirst().orElseThrow();
Map<String, Object> variablesLocalB = runtimeService.getVariablesLocal(taskB.getExecutionId());
assertThat(variablesLocalB).hasSize(2);
assertThat(variablesLocalB.get("commonLocalVar")).isEqualTo("B1");
assertThat(variablesLocalB.get("uniqueLocalVarB")).isEqualTo("B2");
}

@Deployment
public void testLocalVariablesAreAvailableAfterSubProcessWithUserTaskParallelGateway() {
// GIVEN a process with two parallel flows
// GIVEN each flow sets local variables, calls a subprocess and ends with a user task

// WHEN process starts
runtimeService.startProcessInstanceByKey("simplerProcess");

// WHEN and we complete the user tasks inside each subprocess
TaskQuery taskQuery = taskService.createTaskQuery();
List<Task> subProcessUserTasks = taskQuery.list();
assertThat(subProcessUserTasks).hasSize(2);

Task subProcessAUserTask = subProcessUserTasks.stream().filter(task1 -> task1.getName().equals("subProcessA userTask")).findFirst().orElseThrow();
taskService.complete(subProcessAUserTask.getId());

Task subProcessBUserTask = subProcessUserTasks.stream().filter(task1 -> task1.getName().equals("subProcessB userTask")).findFirst().orElseThrow();
taskService.complete(subProcessBUserTask.getId());

// THEN we reach the user tasks on the main process
taskQuery = taskService.createTaskQuery();
List<Task> mainProcessUserTasks = taskQuery.list();
assertThat(mainProcessUserTasks).hasSize(2);

// THEN when reaching userTaskA (flow A), we have 2 local variables
Task mainProcessAUserTask = mainProcessUserTasks.stream().filter(task1 -> task1.getName().equals("user task A")).findFirst().orElseThrow();
Map<String, Object> variablesLocalA = runtimeService.getVariablesLocal(mainProcessAUserTask.getExecutionId());
assertThat(variablesLocalA).hasSize(2);
assertThat(variablesLocalA.get("commonLocalVar")).isEqualTo("A1");
assertThat(variablesLocalA.get("uniqueLocalVarA")).isEqualTo("A2");

// THEN when reaching userTaskB (flow B), we have 2 local variables
Task mainProcessBUserTask = subProcessUserTasks.stream().filter(task1 -> task1.getName().equals("user task B")).findFirst().orElseThrow();
Map<String, Object> variablesLocalB = runtimeService.getVariablesLocal(mainProcessBUserTask.getExecutionId());
assertThat(variablesLocalB).hasSize(2);
assertThat(variablesLocalB.get("commonLocalVar")).isEqualTo("B1");
assertThat(variablesLocalB.get("uniqueLocalVarB")).isEqualTo("B2");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
Expand Down Expand Up @@ -443,4 +444,40 @@ public void testDataObjectScope() {
taskService.complete(currentTask.getId());
assertThat(runtimeService.createProcessInstanceQuery().processInstanceId(pi.getId()).singleResult()).isNull();
}

@Deployment
public void testLocalVariablesAreAvailableAfterSubProcess() {
// GIVEN a process that creates a local variable, calls a subprocess and evaluates the initial local variable

// WHEN process starts
runtimeService.startProcessInstanceByKey("simplerProcess");

// THEN after completing the subprocess, evaluation of the local variable is possible and flow reaches a user task
TaskQuery taskQuery = taskService.createTaskQuery();
Task taskBeforeSubProcess = taskQuery.singleResult();
assertThat(taskBeforeSubProcess.getName()).isEqualTo("user task");
}

@Deployment
public void testLocalVariablesAreAvailableAfterSubProcessParallelGateway() {
// GIVEN a process that creates a local variable, calls a subprocess and evaluates the initial local variable

// WHEN process starts
runtimeService.startProcessInstanceByKey("simplerProcess");

// THEN after completing the subprocess, evaluation of the local variable is possible and flow reaches a user task
TaskQuery taskQuery = taskService.createTaskQuery();
List<Task> tasks = taskQuery.list();
assertThat(tasks).hasSize(2);

Task taskA = tasks.stream().filter(task1 -> task1.getName().equals("user task A")).findFirst().orElseThrow();
Task taskB = tasks.stream().filter(task1 -> task1.getName().equals("user task B")).findFirst().orElseThrow();

List<Execution> listA = runtimeService.createExecutionQuery().executionId(taskA.getExecutionId()).list();
List<Execution> listB = runtimeService.createExecutionQuery().executionId(taskB.getExecutionId()).list();
System.out.println("ff");

// assertThat(taskBeforeSubProcess.getName()).isEqualTo("user task");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?xml version='1.0' encoding='UTF-8'?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef" xmlns:modeler="http://activiti.com/modeler" modeler:version="1.0en" modeler:exportDateTime="20240216110429549" modeler:modelId="2" modeler:modelVersion="2" modeler:modelLastUpdated="1708081436130">
<process id="simplerProcess" name="simplerProcess" isExecutable="true">
<startEvent id="startEvent">
<extensionElements>
<modeler:editor-resource-id><![CDATA[startEvent1]]></modeler:editor-resource-id>
</extensionElements>
</startEvent>
<scriptTask id="script" name="script" scriptFormat="javascript" activiti:autoStoreVariables="false">
<script><![CDATA[execution.setVariableLocal("httpStatus",500);]]></script>
</scriptTask>
<subProcess id="subprocess" name="subProcess">
<startEvent id="subprocess-startEvent"/>
<endEvent id="subprocess-endEvent"/>
<sequenceFlow id="subprocess-startEvent_endEvent" sourceRef="subprocess-startEvent" targetRef="subprocess-endEvent"/>
</subProcess>
<exclusiveGateway id="gateway" default="gateway_endEvent"/>
<userTask id="userTask" name="user task" activiti:assignee="$INITIATOR"/>
<endEvent id="endEvent2"/>
<endEvent id="endEvent1"/>
<sequenceFlow id="startEvent_script" sourceRef="startEvent" targetRef="script"/>
<sequenceFlow id="subProcess_gateway" sourceRef="subprocess" targetRef="gateway"/>
<sequenceFlow id="script_subProcess" sourceRef="script" targetRef="subprocess"/>
<sequenceFlow id="userTask_endEvent" sourceRef="userTask" targetRef="endEvent1"/>
<sequenceFlow id="gateway_userTask" sourceRef="gateway" targetRef="userTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${(httpStatus!=200)}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="gateway_endEvent" sourceRef="gateway" targetRef="endEvent2"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_simplerProcess">
<bpmndi:BPMNPlane bpmnElement="simplerProcess" id="BPMNPlane_simplerProcess">
<bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
<omgdc:Bounds height="30.0" width="30.0" x="210.0" y="157.00000221187435"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="script" id="BPMNShape_script">
<omgdc:Bounds height="80.0" width="100.0" x="315.00000443783705" y="132.00001373616288"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="subprocess" id="BPMNShape_subprocess">
<omgdc:Bounds height="233.0" width="243.0" x="495.0000069737439" y="132.00001559582813"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="gateway" id="BPMNShape_gateway">
<omgdc:Bounds height="40.0" width="40.0" x="803.0000092983253" y="305.0000040010975"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="userTask" id="BPMNShape_userTask">
<omgdc:Bounds height="80.0" width="100.0" x="893.0000105662787" y="285.00001745549343"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEvent2" id="BPMNShape_endEvent2">
<omgdc:Bounds height="28.0" width="28.0" x="809.0000186811809" y="393.00001352483696"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEvent1" id="BPMNShape_endEvent1">
<omgdc:Bounds height="28.0" width="28.0" x="1058.00001289086" y="311.0000040856278"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="subprocess-startEvent" id="BPMNShape_subprocess-startEvent">
<omgdc:Bounds height="30.0" width="30.0" x="595.0000069737439" y="295.00001559582813"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="subprocess-endEvent" id="BPMNShape_subprocess-endEvent">
<omgdc:Bounds height="28.0" width="28.0" x="670.0000069737439" y="296.00001559582813"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="script_subProcess" id="BPMNEdge_script_subProcess">
<omgdi:waypoint x="415.00000443783705" y="172.00001425273655"/>
<omgdi:waypoint x="495.0000069737439" y="172.00001507925447"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gateway_userTask" id="BPMNEdge_gateway_userTask">
<omgdi:waypoint x="842.5798390730412" y="325.4201742263816"/>
<omgdi:waypoint x="893.0000105662787" y="325.2092168447399"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gateway_endEvent" id="BPMNEdge_gateway_endEvent">
<omgdi:waypoint x="823.382727576" y="344.6172857234228"/>
<omgdi:waypoint x="823.0859050137055" y="393.00027697239125"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="userTask_endEvent" id="BPMNEdge_userTask_endEvent">
<omgdi:waypoint x="993.0000105662787" y="325.00001227337503"/>
<omgdi:waypoint x="1058.00001289086" y="325.00000553662096"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="startEvent_script" id="BPMNEdge_startEvent_script">
<omgdi:waypoint x="239.99999999999994" y="172.0000034466195"/>
<omgdi:waypoint x="315.00000443783705" y="172.00000962034568"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="subProcess_gateway" id="BPMNEdge_subProcess_gateway">
<omgdi:waypoint x="738.0000069737439" y="325.00001130148354"/>
<omgdi:waypoint x="803.0000110160629" y="325.0000057188352"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="subprocess-startEvent_endEvent" id="BPMNEdge_subprocess-startEvent_endEvent">
<omgdi:waypoint x="625.0000069737439" y="310.00001559582813"/>
<omgdi:waypoint x="670.0000069737439" y="310.00001559582813"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>