Skip to content

Commit 232dd8a

Browse files
committed
Fix TraceableObject passed by reference to child instance, which leads to unwanted changes in original variable
1 parent ef98433 commit 232dd8a

23 files changed

Lines changed: 1908 additions & 1 deletion

File tree

modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,18 @@
4848
import org.flowable.entitylink.api.EntityLinkType;
4949
import org.flowable.entitylink.api.HierarchyType;
5050
import org.flowable.entitylink.api.history.HistoricEntityLink;
51+
import org.flowable.job.api.Job;
5152
import org.flowable.task.api.Task;
5253
import org.flowable.variable.api.history.HistoricVariableInstance;
5354
import org.flowable.variable.api.persistence.entity.VariableInstance;
5455
import org.flowable.variable.service.impl.types.JsonType;
5556
import org.junit.Test;
5657

58+
import com.fasterxml.jackson.databind.JsonNode;
59+
import com.fasterxml.jackson.databind.ObjectMapper;
60+
import com.fasterxml.jackson.databind.node.ArrayNode;
61+
import com.fasterxml.jackson.databind.node.ObjectNode;
62+
5763
import net.javacrumbs.jsonunit.core.Option;
5864

5965
/**
@@ -1329,6 +1335,98 @@ public void testGetCaseInstanceByParentScopeId() {
13291335
}
13301336
}
13311337

1338+
@Test
1339+
@CmmnDeployment(resources = "org/flowable/cmmn/test/CaseTaskTest.childCaseWithAsyncFirstStep.cmmn")
1340+
public void testCaseTaskInMappingWithArrayNodeAsyncFirstPlanItem() {
1341+
1342+
Deployment deployment = processEngineRepositoryService.createDeployment()
1343+
.addClasspathResource("org/flowable/cmmn/test/CaseTaskTest.testCaseTaskMappingMappingWithArrayNode.bpmn20.xml")
1344+
.deploy();
1345+
1346+
try {
1347+
1348+
ObjectMapper objectMapper = processEngineConfiguration.getObjectMapper();
1349+
ArrayNode arrayNode = objectMapper.createArrayNode();
1350+
for (int i = 0; i < 10; i++) {
1351+
ObjectNode arrayElement = objectMapper.createObjectNode();
1352+
arrayElement.put("field", "value-" + i);
1353+
arrayNode.add(arrayElement);
1354+
}
1355+
1356+
Map<String, Object> variables = new HashMap<>();
1357+
variables.put("myRootVariable", arrayNode);
1358+
ProcessInstance processInstance = processEngineRuntimeService.startProcessInstanceByKey("caseTaskMapping", variables);
1359+
1360+
List<CaseInstance> caseInstances = cmmnRuntimeService.createCaseInstanceQuery().list();
1361+
assertThat(caseInstances).hasSize(10);
1362+
1363+
// The script task is async, so the change will only have happened after the job is executed
1364+
for (CaseInstance caseInstance : caseInstances) {
1365+
JsonNode jsonNode = (JsonNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "myInMappedVariable");
1366+
assertThat(jsonNode.path("field").asText()).startsWith("value-");
1367+
}
1368+
1369+
for (Job job : cmmnManagementService.createJobQuery().list()) {
1370+
cmmnManagementService.executeJob(job.getId());
1371+
}
1372+
1373+
for (CaseInstance caseInstance : caseInstances) {
1374+
JsonNode jsonNode = (JsonNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "myInMappedVariable");
1375+
assertThat(jsonNode.path("field").asText()).startsWith("CHANGED");
1376+
}
1377+
1378+
ArrayNode rootArrayNode = (ArrayNode) processEngineRuntimeService.getVariable(processInstance.getId(), "myRootVariable");
1379+
assertThat(rootArrayNode).hasSize(10);
1380+
for (JsonNode rootArrayNodeElement : rootArrayNode) {
1381+
assertThat(rootArrayNodeElement.path("field").asText()).startsWith("value-");
1382+
}
1383+
1384+
} finally {
1385+
processEngineRepositoryService.deleteDeployment(deployment.getId(), true);
1386+
}
1387+
}
1388+
1389+
@Test
1390+
@CmmnDeployment(resources = "org/flowable/cmmn/test/CaseTaskTest.childCaseWithSyncFirstStep.cmmn")
1391+
public void testCaseTaskInMappingWithArrayNodeSyncFirstPlanItem() {
1392+
1393+
Deployment deployment = processEngineRepositoryService.createDeployment()
1394+
.addClasspathResource("org/flowable/cmmn/test/CaseTaskTest.testCaseTaskMappingMappingWithArrayNode.bpmn20.xml")
1395+
.deploy();
1396+
1397+
try {
1398+
1399+
ObjectMapper objectMapper = processEngineConfiguration.getObjectMapper();
1400+
ArrayNode arrayNode = objectMapper.createArrayNode();
1401+
for (int i = 0; i < 10; i++) {
1402+
ObjectNode arrayElement = objectMapper.createObjectNode();
1403+
arrayElement.put("field", "value-" + i);
1404+
arrayNode.add(arrayElement);
1405+
}
1406+
1407+
Map<String, Object> variables = new HashMap<>();
1408+
variables.put("myRootVariable", arrayNode);
1409+
ProcessInstance processInstance = processEngineRuntimeService.startProcessInstanceByKey("caseTaskMapping", variables);
1410+
1411+
List<CaseInstance> caseInstances = cmmnRuntimeService.createCaseInstanceQuery().list();
1412+
assertThat(caseInstances).hasSize(10);
1413+
1414+
for (CaseInstance caseInstance : caseInstances) {
1415+
JsonNode jsonNode = (JsonNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "myInMappedVariable");
1416+
assertThat(jsonNode.path("field").asText()).startsWith("CHANGED");
1417+
}
1418+
1419+
ArrayNode rootArrayNode = (ArrayNode) processEngineRuntimeService.getVariable(processInstance.getId(), "myRootVariable");
1420+
assertThat(rootArrayNode).hasSize(10);
1421+
for (JsonNode rootArrayNodeElement : rootArrayNode) {
1422+
assertThat(rootArrayNodeElement.path("field").asText()).startsWith("value-");
1423+
}
1424+
1425+
} finally {
1426+
processEngineRepositoryService.deleteDeployment(deployment.getId(), true);
1427+
}
1428+
}
1429+
13321430
static class ClearExecutionReferenceCmd implements Command<Void> {
13331431

13341432
@Override

modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.flowable.entitylink.api.EntityLinkType;
5656
import org.flowable.entitylink.api.HierarchyType;
5757
import org.flowable.entitylink.api.history.HistoricEntityLink;
58+
import org.flowable.job.api.Job;
5859
import org.flowable.task.api.Task;
5960
import org.flowable.task.api.history.HistoricTaskInstance;
6061
import org.flowable.task.api.history.HistoricTaskLogEntry;
@@ -64,6 +65,9 @@
6465
import org.junit.Before;
6566
import org.junit.Test;
6667

68+
import com.fasterxml.jackson.databind.JsonNode;
69+
import com.fasterxml.jackson.databind.ObjectMapper;
70+
import com.fasterxml.jackson.databind.node.ArrayNode;
6771
import com.fasterxml.jackson.databind.node.ObjectNode;
6872

6973
/**
@@ -2096,4 +2100,110 @@ public void testGetProcessInstanceByParentScopeId() {
20962100
assertThat(subCasePlanItemInstance.getReferenceId()).isEqualTo(processInstance.getId());
20972101

20982102
}
2103+
2104+
@Test
2105+
@CmmnDeployment(resources = {
2106+
"org/flowable/cmmn/test/ProcessTaskTest.caseWithInAndOutMapping.cmmn"
2107+
})
2108+
public void testInMappingArrayNodeWithAsyncFirstStep() {
2109+
2110+
Deployment deployment = processEngine.getRepositoryService().createDeployment()
2111+
.addClasspathResource("org/flowable/cmmn/test/ProcessTaskTest.caseWithInAndOutMappingChildProcessAsync.bpmn20.xml")
2112+
.deploy();
2113+
2114+
try {
2115+
ObjectMapper objectMapper = cmmnEngineConfiguration.getObjectMapper();
2116+
ArrayNode arrayNode = objectMapper.createArrayNode();
2117+
for (int i = 0; i < 10; i++) {
2118+
ObjectNode arrayElement = objectMapper.createObjectNode();
2119+
arrayElement.put("field", "value-" + i);
2120+
arrayNode.add(arrayElement);
2121+
}
2122+
2123+
CaseInstance rootCaseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
2124+
.caseDefinitionKey("caseTaskMapping")
2125+
.variable("myRootVariable", arrayNode)
2126+
.start();
2127+
2128+
List<ProcessInstance> processInstances = processEngineRuntimeService.createProcessInstanceQuery().list();
2129+
processInstances.removeIf(processInstance -> processInstance.getCallbackId() == null);
2130+
assertThat(processInstances).hasSize(10);
2131+
2132+
// As the first step is an async step, there should be one job.
2133+
// The variable myElementVariable is mapped as 'myInMappedVariable' into the called process instance
2134+
// The variable at this point should not have changed at this point.
2135+
for (ProcessInstance processInstance : processInstances) {
2136+
JsonNode inMappedJsonNodeVariable = (JsonNode) processEngineRuntimeService.getVariable(processInstance.getId(), "myInMappedVariable");
2137+
assertThat(inMappedJsonNodeVariable.path("field").asText()).startsWith("value-");
2138+
}
2139+
2140+
// Executing the async job should now change the variable in the child process instances
2141+
List<Job> jobs = processEngineManagementService.createJobQuery().list();
2142+
assertThat(jobs).hasSize(10);
2143+
for (Job job : jobs) {
2144+
processEngineManagementService.executeJob(job.getId());
2145+
}
2146+
2147+
for (ProcessInstance processInstance : processInstances) {
2148+
JsonNode inMappedJsonNodeVariable = (JsonNode) processEngineRuntimeService.getVariable(processInstance.getId(), "myInMappedVariable");
2149+
assertThat(inMappedJsonNodeVariable.path("field").asText()).startsWith("CHANGED");
2150+
}
2151+
2152+
// The variable should not have changed on the root process instance level
2153+
ArrayNode rootArrayNode = (ArrayNode) cmmnRuntimeService.getVariable(rootCaseInstance.getId(), "myRootVariable");
2154+
assertThat(rootArrayNode).hasSize(10);
2155+
for (JsonNode rootArrayNodeElement : rootArrayNode) {
2156+
assertThat(rootArrayNodeElement.path("field").asText()).startsWith("value-");
2157+
}
2158+
} finally {
2159+
processEngine.getRepositoryService().deleteDeployment(deployment.getId(), true);
2160+
}
2161+
2162+
}
2163+
2164+
@Test
2165+
@CmmnDeployment(resources = {
2166+
"org/flowable/cmmn/test/ProcessTaskTest.caseWithInAndOutMapping.cmmn"
2167+
})
2168+
public void testInMappingArrayNodeWithSyncFirstStep() {
2169+
2170+
Deployment deployment = processEngine.getRepositoryService().createDeployment()
2171+
.addClasspathResource("org/flowable/cmmn/test/ProcessTaskTest.caseWithInAndOutMappingChildProcessSync.bpmn20.xml")
2172+
.deploy();
2173+
2174+
try {
2175+
2176+
ObjectMapper objectMapper = cmmnEngineConfiguration.getObjectMapper();
2177+
ArrayNode arrayNode = objectMapper.createArrayNode();
2178+
for (int i = 0; i < 10; i++) {
2179+
ObjectNode arrayElement = objectMapper.createObjectNode();
2180+
arrayElement.put("field", "value-" + i);
2181+
arrayNode.add(arrayElement);
2182+
}
2183+
2184+
CaseInstance rootCaseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
2185+
.caseDefinitionKey("caseTaskMapping")
2186+
.variable("myRootVariable", arrayNode)
2187+
.start();
2188+
2189+
List<ProcessInstance> processInstances = processEngineRuntimeService.createProcessInstanceQuery().list();
2190+
processInstances.removeIf(processInstance -> processInstance.getCallbackId() == null);
2191+
assertThat(processInstances).hasSize(10);
2192+
2193+
for (ProcessInstance processInstance : processInstances) {
2194+
JsonNode inMappedJsonNodeVariable = (JsonNode) processEngineRuntimeService.getVariable(processInstance.getId(), "myInMappedVariable");
2195+
assertThat(inMappedJsonNodeVariable.path("field").asText()).startsWith("CHANGED");
2196+
}
2197+
2198+
// The variable should not have changed on the root process instance level
2199+
ArrayNode rootArrayNode = (ArrayNode) cmmnRuntimeService.getVariable(rootCaseInstance.getId(), "myRootVariable");
2200+
assertThat(rootArrayNode).hasSize(10);
2201+
for (JsonNode rootArrayNodeElement : rootArrayNode) {
2202+
assertThat(rootArrayNodeElement.path("field").asText()).startsWith("value-");
2203+
}
2204+
} finally {
2205+
processEngine.getRepositoryService().deleteDeployment(deployment.getId(), true);
2206+
}
2207+
}
2208+
20992209
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" xmlns:design="http://flowable.org/design" targetNamespace="http://flowable.org/cmmn" design:palette="flowable-work-case-palette">
3+
<case id="caseWithInAndOutMapping" name="Case with in and out mapping" flowable:initiatorVariableName="initiator" flowable:candidateStarterGroups="flowableUser">
4+
<casePlanModel id="onecaseplanmodel1" name="Case plan model" flowable:formFieldValidation="false">
5+
<extensionElements>
6+
<flowable:default-menu-navigation-size><![CDATA[expanded]]></flowable:default-menu-navigation-size>
7+
<flowable:work-form-field-validation><![CDATA[false]]></flowable:work-form-field-validation>
8+
<design:stencilid><![CDATA[CasePlanModel]]></design:stencilid>
9+
</extensionElements>
10+
<planItem id="planItemHumanTask_1" name="Human task" definitionRef="HumanTask_1">
11+
<entryCriterion id="EntryCriterion_3" sentryRef="sentryEntryCriterion_3"></entryCriterion>
12+
</planItem>
13+
<planItem id="planItemScriptTask_2" name="Script task" definitionRef="ScriptTask_2"></planItem>
14+
<sentry id="sentryEntryCriterion_3">
15+
<extensionElements>
16+
<design:stencilid><![CDATA[EntryCriterion]]></design:stencilid>
17+
</extensionElements>
18+
<planItemOnPart id="sentryOnPartEntryCriterion_3" sourceRef="planItemScriptTask_2">
19+
<standardEvent>complete</standardEvent>
20+
</planItemOnPart>
21+
</sentry>
22+
<humanTask id="HumanTask_1" name="Human task" flowable:assignee="${initiator}" flowable:formFieldValidation="false">
23+
<extensionElements>
24+
<flowable:task-candidates-type><![CDATA[all]]></flowable:task-candidates-type>
25+
<design:stencilid><![CDATA[HumanTask]]></design:stencilid>
26+
<design:stencilsuperid><![CDATA[Task]]></design:stencilsuperid>
27+
</extensionElements>
28+
</humanTask>
29+
<task id="ScriptTask_2" name="Script task" flowable:async="true" flowable:exclusive="true" flowable:type="script" flowable:scriptFormat="groovy">
30+
<extensionElements>
31+
<design:stencilid><![CDATA[ScriptTask]]></design:stencilid>
32+
<design:stencilsuperid><![CDATA[Task]]></design:stencilsuperid>
33+
<flowable:field name="script">
34+
<flowable:string><![CDATA[var myVar = caseInstance.getVariable('myInMappedVariable');
35+
myVar.put("field", "CHANGED");]]></flowable:string>
36+
</flowable:field>
37+
</extensionElements>
38+
</task>
39+
</casePlanModel>
40+
</case>
41+
<cmmndi:CMMNDI>
42+
<cmmndi:CMMNDiagram id="CMMNDiagram_caseWithInAndOutMapping">
43+
<cmmndi:CMMNShape id="CMMNShape_onecaseplanmodel1" cmmnElementRef="onecaseplanmodel1">
44+
<dc:Bounds height="679.0" width="830.0" x="270.0" y="120.0"></dc:Bounds>
45+
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
46+
</cmmndi:CMMNShape>
47+
<cmmndi:CMMNShape id="CMMNShape_planItemHumanTask_1" cmmnElementRef="planItemHumanTask_1">
48+
<dc:Bounds height="80.0" width="100.0" x="696.0" y="248.0"></dc:Bounds>
49+
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
50+
</cmmndi:CMMNShape>
51+
<cmmndi:CMMNShape id="CMMNShape_EntryCriterion_3" cmmnElementRef="EntryCriterion_3">
52+
<dc:Bounds height="28.0" width="18.0" x="687.0" y="259.0"></dc:Bounds>
53+
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
54+
</cmmndi:CMMNShape>
55+
<cmmndi:CMMNShape id="CMMNShape_planItemScriptTask_2" cmmnElementRef="planItemScriptTask_2">
56+
<dc:Bounds height="80.0" width="100.0" x="425.0" y="271.0"></dc:Bounds>
57+
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
58+
</cmmndi:CMMNShape>
59+
<cmmndi:CMMNEdge id="CMMNEdge_Connector_4" cmmnElementRef="planItemScriptTask_2" targetCMMNElementRef="EntryCriterion_3">
60+
<di:extension>
61+
<flowable:docker type="source" x="50.0" y="40.0"></flowable:docker>
62+
<flowable:docker type="target" x="9.0" y="14.0"></flowable:docker>
63+
</di:extension>
64+
<di:waypoint x="525.0" y="311.0"></di:waypoint>
65+
<di:waypoint x="606.0" y="311.0"></di:waypoint>
66+
<di:waypoint x="606.0" y="273.0"></di:waypoint>
67+
<di:waypoint x="687.0" y="273.0"></di:waypoint>
68+
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
69+
</cmmndi:CMMNEdge>
70+
</cmmndi:CMMNDiagram>
71+
</cmmndi:CMMNDI>
72+
</definitions>

0 commit comments

Comments
 (0)