From 9b4d6333ee006c5200629f356fe2bfdec78f7ca8 Mon Sep 17 00:00:00 2001
From: BertMatthys <84137009+BertMatthys@users.noreply.github.com>
Date: Mon, 24 Aug 2026 15:40:29 +0200
Subject: [PATCH 1/2] Add inheritVariables property to CMMN CaseTask and
ProcessTask
---
.../cmmn/converter/CaseTaskXmlConverter.java | 5 ++++
.../cmmn/converter/CmmnXmlConstants.java | 1 +
.../converter/ProcessTaskXmlConverter.java | 5 ++++
.../export/AbstractChildTaskExport.java | 3 +++
.../CaseTaskCmmnXmlConverterTest.java | 1 +
.../ProcessTask2CmmnXmlConverterTest.java | 1 +
.../test/cmmn/converter/case-task.cmmn | 2 +-
.../test/cmmn/converter/process-task2.cmmn | 2 +-
.../flowable/cmmn/test/ProcessTaskTest.java | 27 +++++++++++++++++++
...kTest.testProcessTaskInheritVariables.cmmn | 19 +++++++++++++
.../impl/CaseTaskActivityBehavior.java | 3 ++-
.../impl/ChildTaskActivityBehavior.java | 13 +++++++++
.../impl/ProcessTaskActivityBehavior.java | 3 ++-
.../cmmn/test/runtime/CaseTaskTest.java | 26 ++++++++++++++++++
...TaskTest.testCaseTaskInheritVariables.cmmn | 16 +++++++++++
.../org/flowable/cmmn/model/ChildTask.java | 9 +++++++
16 files changed, 132 insertions(+), 4 deletions(-)
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
index 4a0bd67fd22..6edd7c848e5 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
@@ -44,6 +44,11 @@ protected CmmnElement convert(XMLStreamReader xtr, ConversionHelper conversionHe
caseTask.setInheritBusinessKey(Boolean.parseBoolean(inheritBusinessKey));
}
+ String inheritVariables = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_INHERIT_VARIABLES);
+ if (inheritVariables != null) {
+ caseTask.setInheritVariables(Boolean.parseBoolean(inheritVariables));
+ }
+
String fallbackToDefaultTenantValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT);
if (fallbackToDefaultTenantValue != null) {
caseTask.setFallbackToDefaultTenant(Boolean.valueOf(fallbackToDefaultTenantValue));
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
index fce5fd993ce..f1bf6797d1c 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
@@ -142,6 +142,7 @@ public interface CmmnXmlConstants {
String ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT = "fallbackToDefaultTenant";
String ATTRIBUTE_BUSINESS_KEY = "businessKey";
String ATTRIBUTE_INHERIT_BUSINESS_KEY = "inheritBusinessKey";
+ String ATTRIBUTE_INHERIT_VARIABLES = "inheritVariables";
String ATTRIBUTE_ID_VARIABLE_NAME = "idVariableName";
String ELEMENT_CASE_REF_EXPRESSION = "caseRefExpression";
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
index c614f5fdc3f..67aab2d3f09 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
@@ -44,6 +44,11 @@ protected CmmnElement convert(XMLStreamReader xtr, ConversionHelper conversionHe
processTask.setInheritBusinessKey(Boolean.parseBoolean(inheritBusinessKey));
}
+ String inheritVariables = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_INHERIT_VARIABLES);
+ if (inheritVariables != null) {
+ processTask.setInheritVariables(Boolean.parseBoolean(inheritVariables));
+ }
+
String fallbackToDefaultTenantValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT);
if (fallbackToDefaultTenantValue != null) {
processTask.setFallbackToDefaultTenant(Boolean.parseBoolean(fallbackToDefaultTenantValue));
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
index d90c74482c3..52a28773b6c 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
@@ -32,6 +32,9 @@ protected void writePlanItemDefinitionSpecificAttributes(T planItemDefinition, X
if (planItemDefinition.isInheritBusinessKey()) {
xtw.writeAttribute(FLOWABLE_EXTENSIONS_PREFIX, FLOWABLE_EXTENSIONS_NAMESPACE, ATTRIBUTE_INHERIT_BUSINESS_KEY, String.valueOf(planItemDefinition.isInheritBusinessKey()));
}
+ if (planItemDefinition.isInheritVariables()) {
+ xtw.writeAttribute(FLOWABLE_EXTENSIONS_PREFIX, FLOWABLE_EXTENSIONS_NAMESPACE, ATTRIBUTE_INHERIT_VARIABLES, String.valueOf(planItemDefinition.isInheritVariables()));
+ }
}
@Override
diff --git a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
index ba068d43693..5673f927e53 100644
--- a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
+++ b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
@@ -52,6 +52,7 @@ public void validateModel(CmmnModel cmmnModel) {
assertThat(task1.getCaseRefExpression()).isEqualTo("caseDefinitionKey");
assertThat(task1.getFallbackToDefaultTenant()).isTrue();
assertThat(task1.isSameDeployment()).isTrue();
+ assertThat(task1.isInheritVariables()).isTrue();
assertThat(task1.getInParameters())
.extracting(IOParameter::getSource, IOParameter::getTarget)
diff --git a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
index f17798afaa8..3d9085494e5 100644
--- a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
+++ b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
@@ -49,6 +49,7 @@ public void validateModel(CmmnModel cmmnModel) {
.isInstanceOfSatisfying(ProcessTask.class, task1 -> {
assertThat(task1.getProcessRefExpression()).isEqualTo("myTestProcess");
assertThat((task1.isSameDeployment())).isTrue();
+ assertThat(task1.isInheritVariables()).isTrue();
assertThat(task1.getInParameters()).isEmpty();
assertThat(task1.getOutParameters()).isEmpty();
diff --git a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
index c3bc7d5baff..39e9965347f 100644
--- a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
+++ b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
@@ -21,7 +21,7 @@
-
+
diff --git a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
index 2a4754d68f4..31dcabbacef 100644
--- a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
+++ b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
@@ -17,7 +17,7 @@
-
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
index aa6f07e8267..d7edbbc7869 100644
--- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
+++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
@@ -1015,6 +1015,33 @@ public void testIOParameterCombinations() {
.hasSize(4 + 4); // 4 from in mapping, 4 from task complete
}
+ @Test
+ @CmmnDeployment
+ @org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/oneTaskProcess.bpmn20.xml")
+ public void testProcessTaskInheritVariables() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .variable("caseVariableB", "world")
+ .variable("caseVariableC", 42)
+ .start();
+
+ PlanItemInstance processTaskPlanItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
+ .caseInstanceId(caseInstance.getId())
+ .planItemDefinitionType(PlanItemDefinitionType.PROCESS_TASK)
+ .singleResult();
+ String processInstanceId = processTaskPlanItemInstance.getReferenceId();
+ ProcessInstance processInstance = processEngineRuntimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+ assertThat(processInstance).isNotNull();
+
+ // All case variables are inherited into the child process instance (like a BPMN call activity)
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableA")).isEqualTo("hello");
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableC")).isEqualTo(42);
+
+ // An explicit in parameter takes precedence over the inherited variable
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableB")).isEqualTo("overridden");
+ }
+
@Test
@CmmnDeployment
public void testProcessTaskWithSkipExpressions() {
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
new file mode 100644
index 00000000000..7062df23cc3
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
index f62dbd9eb36..0d1bcfeb442 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
@@ -57,7 +57,8 @@ public class CaseTaskActivityBehavior extends ChildTaskActivityBehavior implemen
protected CaseTask caseTask;
public CaseTaskActivityBehavior(Expression caseRefExpression, CaseTask caseTask) {
- super(caseTask.isBlocking(), caseTask.getBlockingExpression(), caseTask.getInParameters(), caseTask.getOutParameters());
+ super(caseTask.isBlocking(), caseTask.getBlockingExpression(), caseTask.getInParameters(), caseTask.getOutParameters(),
+ caseTask.isInheritVariables());
this.caseRefExpression = caseRefExpression;
this.caseRef = caseTask.getCaseRef();
this.fallbackToDefaultTenant = caseTask.getFallbackToDefaultTenant();
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
index 866bc0edd00..f9d12caf70e 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
@@ -43,6 +43,7 @@ public abstract class ChildTaskActivityBehavior extends CoreCmmnTriggerableActiv
protected String isBlockingExpression;
protected List inParameters;
protected List outParameters;
+ protected boolean inheritVariables;
public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression) {
this.isBlocking = isBlocking;
@@ -50,9 +51,15 @@ public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression
}
public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression, List inParameters, List outParameters) {
+ this(isBlocking, isBlockingExpression, inParameters, outParameters, false);
+ }
+
+ public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression, List inParameters, List outParameters,
+ boolean inheritVariables) {
this(isBlocking, isBlockingExpression);
this.inParameters = inParameters;
this.outParameters = outParameters;
+ this.inheritVariables = inheritVariables;
}
@Override
@@ -83,6 +90,12 @@ protected void handleInParameters(PlanItemInstanceEntity planItemInstanceEntity,
CmmnEngineConfiguration cmmnEngineConfiguration, Map inParametersMap,
ExpressionManager expressionManager) {
+ if (inheritVariables) {
+ // Inherit all variables from the parent. Explicit in parameters are
+ // applied afterwards, so they take precedence over the inherited variables.
+ inParametersMap.putAll(planItemInstanceEntity.getVariables());
+ }
+
IOParameterUtil.processInParameters(inParameters, planItemInstanceEntity, inParametersMap, expressionManager);
}
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
index c47331e67fa..7119c012ede 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
@@ -57,7 +57,8 @@ public class ProcessTaskActivityBehavior extends ChildTaskActivityBehavior imple
protected ProcessTask processTask;
public ProcessTaskActivityBehavior(Process process, Expression processRefExpression, ProcessTask processTask) {
- super(processTask.isBlocking(), processTask.getBlockingExpression(), processTask.getInParameters(), processTask.getOutParameters());
+ super(processTask.isBlocking(), processTask.getBlockingExpression(), processTask.getInParameters(), processTask.getOutParameters(),
+ processTask.isInheritVariables());
this.process = process;
this.processRefExpression = processRefExpression;
this.processRef = processTask.getProcessRef();
diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
index 8ff291a8626..bd24368a082 100644
--- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
+++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
@@ -1236,6 +1236,32 @@ public void testWithInheritBusinessKeyButWithoutBusinessKey() {
cmmnRuntimeService.triggerPlanItemInstance(planItemInstance.getId());
}
+ @Test
+ @CmmnDeployment(resources = {
+ "org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn",
+ "org/flowable/cmmn/test/runtime/oneTaskCase.cmmn"
+ })
+ public void testCaseTaskInheritVariables() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .variable("caseVariableB", "world")
+ .variable("caseVariableC", 42)
+ .start();
+
+ CaseInstance subCase = cmmnRuntimeService.createCaseInstanceQuery()
+ .caseDefinitionKey("oneTaskCase")
+ .singleResult();
+ assertThat(subCase).isNotNull();
+
+ // All parent case variables are inherited into the child case instance (like a BPMN call activity)
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableA")).isEqualTo("hello");
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableC")).isEqualTo(42);
+
+ // An explicit in parameter takes precedence over the inherited variable
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableB")).isEqualTo("overridden");
+ }
+
@Test
@CmmnDeployment
public void testIdVariableName() {
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
new file mode 100644
index 00000000000..adaeedb0ffd
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
index 59f705488ba..e7017b1c0d5 100644
--- a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
+++ b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
@@ -22,6 +22,7 @@ public class ChildTask extends Task implements HasInParameters, HasOutParameters
protected String businessKey;
protected boolean inheritBusinessKey;
+ protected boolean inheritVariables;
protected List inParameters = new ArrayList<>();
protected List outParameters = new ArrayList<>();
@@ -41,6 +42,14 @@ public void setInheritBusinessKey(boolean inheritBusinessKey) {
this.inheritBusinessKey = inheritBusinessKey;
}
+ public boolean isInheritVariables() {
+ return inheritVariables;
+ }
+
+ public void setInheritVariables(boolean inheritVariables) {
+ this.inheritVariables = inheritVariables;
+ }
+
@Override
public List getInParameters() {
return inParameters;
From f4d02e9cdfff0a6ba59fa92803741fc1a172bd09 Mon Sep 17 00:00:00 2001
From: BertMatthys <84137009+BertMatthys@users.noreply.github.com>
Date: Fri, 28 Aug 2026 15:26:56 +0200
Subject: [PATCH 2/2] Handle transient and json variables correctly when
passing to child instance
---
.../bpmn/constants/BpmnXMLConstants.java | 1 +
.../converter/ServiceTaskXMLConverter.java | 5 ++
.../xml/CaseServiceTaskConverterTest.java | 1 +
.../src/test/resources/caseservicetask.bpmn | 2 +-
.../flowable/bpmn/model/CaseServiceTask.java | 10 +++
.../impl/cmmn/DefaultCaseInstanceService.java | 6 +-
.../DefaultProcessInstanceService.java | 10 ++-
.../org/flowable/cmmn/test/CaseTaskTest.java | 87 +++++++++++++++++++
.../flowable/cmmn/test/ProcessTaskTest.java | 41 ++++++++-
...TaskTest.testCaseTaskInheritVariables.cmmn | 16 ++++
...kTest.testProcessTaskInheritVariables.cmmn | 3 +-
...stProcessTaskInheritVariablesDisabled.cmmn | 16 ++++
...TaskInheritVariablesDisabledChildCase.cmmn | 13 +++
...InheritVariablesDisabledProcess.bpmn20.xml | 20 +++++
...caseTaskInheritVariablesProcess.bpmn20.xml | 24 +++++
.../inheritVariablesChildProcess.bpmn20.xml | 14 +++
...itVariablesDisabledChildProcess.bpmn20.xml | 14 +++
.../impl/CaseTaskActivityBehavior.java | 6 +-
.../impl/ChildTaskActivityBehavior.java | 18 +++-
.../impl/ProcessTaskActivityBehavior.java | 7 +-
.../impl/process/ProcessInstanceService.java | 4 +-
.../cmmn/test/runtime/CaseTaskTest.java | 38 +++++++-
...aseTaskTest.inheritVariablesChildCase.cmmn | 13 +++
...TaskTest.testCaseTaskInheritVariables.cmmn | 2 +-
....testCaseTaskInheritVariablesDisabled.cmmn | 12 +++
.../bpmn/behavior/CallActivityBehavior.java | 7 +-
.../behavior/CaseTaskActivityBehavior.java | 21 ++++-
.../engine/impl/cmmn/CaseInstanceService.java | 2 +-
28 files changed, 390 insertions(+), 23 deletions(-)
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/CaseTaskTest.testCaseTaskInheritVariables.cmmn
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariablesDisabled.cmmn
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledChildCase.cmmn
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledProcess.bpmn20.xml
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesProcess.bpmn20.xml
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesChildProcess.bpmn20.xml
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesDisabledChildProcess.bpmn20.xml
create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.inheritVariablesChildCase.cmmn
create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariablesDisabled.cmmn
diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/constants/BpmnXMLConstants.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/constants/BpmnXMLConstants.java
index 67a59b8247f..6a93b87b890 100644
--- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/constants/BpmnXMLConstants.java
+++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/constants/BpmnXMLConstants.java
@@ -245,6 +245,7 @@ public interface BpmnXMLConstants {
public static final String ATTRIBUTE_BUSINESS_KEY = "businessKey";
public static final String ATTRIBUTE_INHERIT_BUSINESS_KEY = "inheritBusinessKey";
+ public static final String ATTRIBUTE_INHERIT_VARIABLES = "inheritVariables";
public static final String ATTRIBUTE_SAME_DEPLOYMENT = "sameDeployment";
public static final String ATTRIBUTE_CALLED_ELEMENT_BINDING = "calledElementBinding";
public static final String ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT = "fallbackToDefaultTenant";
diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/ServiceTaskXMLConverter.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/ServiceTaskXMLConverter.java
index 2a12c652376..31cc245c387 100644
--- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/ServiceTaskXMLConverter.java
+++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/ServiceTaskXMLConverter.java
@@ -76,6 +76,7 @@ public class ServiceTaskXMLConverter extends BaseBpmnXMLConverter {
new ExtensionAttribute(ATTRIBUTE_CASE_TASK_CASE_INSTANCE_NAME),
new ExtensionAttribute(ATTRIBUTE_BUSINESS_KEY),
new ExtensionAttribute(ATTRIBUTE_INHERIT_BUSINESS_KEY),
+ new ExtensionAttribute(ATTRIBUTE_INHERIT_VARIABLES),
new ExtensionAttribute(ATTRIBUTE_SAME_DEPLOYMENT),
new ExtensionAttribute(ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT),
new ExtensionAttribute(ATTRIBUTE_ID_VARIABLE_NAME));
@@ -236,6 +237,9 @@ protected void writeCaseServiceTaskAdditionalAttributes(BaseElement element, Bpm
if (caseServiceTask.isInheritBusinessKey()) {
writeQualifiedAttribute(ATTRIBUTE_INHERIT_BUSINESS_KEY, "true", xtw);
}
+ if (caseServiceTask.isInheritVariables()) {
+ writeQualifiedAttribute(ATTRIBUTE_INHERIT_VARIABLES, "true", xtw);
+ }
if (caseServiceTask.isSameDeployment()) {
writeQualifiedAttribute(ATTRIBUTE_SAME_DEPLOYMENT, "true", xtw);
}
@@ -432,6 +436,7 @@ protected void convertCaseServiceTaskXMLProperties(CaseServiceTask caseServiceTa
caseServiceTask.setBusinessKey(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_BUSINESS_KEY, xtr));
caseServiceTask.setInheritBusinessKey(Boolean.parseBoolean(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_INHERIT_BUSINESS_KEY, xtr)));
+ caseServiceTask.setInheritVariables(Boolean.parseBoolean(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_INHERIT_VARIABLES, xtr)));
caseServiceTask.setSameDeployment(Boolean.valueOf(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_SAME_DEPLOYMENT, xtr)));
caseServiceTask.setFallbackToDefaultTenant(Boolean.valueOf(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT, xtr)));
caseServiceTask.setCaseInstanceIdVariableName(BpmnXMLUtil.getAttributeValue(ATTRIBUTE_ID_VARIABLE_NAME, xtr));
diff --git a/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/CaseServiceTaskConverterTest.java b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/CaseServiceTaskConverterTest.java
index 809420b30ab..77000c8ca45 100644
--- a/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/CaseServiceTaskConverterTest.java
+++ b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/CaseServiceTaskConverterTest.java
@@ -37,6 +37,7 @@ void validateModel(BpmnModel model) {
assertThat(caseServiceTask.isFallbackToDefaultTenant()).isTrue();
assertThat(caseServiceTask.isSameDeployment()).isFalse();
+ assertThat(caseServiceTask.isInheritVariables()).isTrue();
List parameters = caseServiceTask.getInParameters();
assertThat(parameters)
diff --git a/modules/flowable-bpmn-converter/src/test/resources/caseservicetask.bpmn b/modules/flowable-bpmn-converter/src/test/resources/caseservicetask.bpmn
index b36c3c4b8b1..a783a913836 100644
--- a/modules/flowable-bpmn-converter/src/test/resources/caseservicetask.bpmn
+++ b/modules/flowable-bpmn-converter/src/test/resources/caseservicetask.bpmn
@@ -9,7 +9,7 @@
targetNamespace="http://www.flowable.org/test">
-
+
diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java
index bc6460f439a..4940d1af81f 100644
--- a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java
+++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java
@@ -25,6 +25,7 @@ public class CaseServiceTask extends ServiceTask implements HasOutParameters, Ha
protected boolean sameDeployment;
protected String businessKey;
protected boolean inheritBusinessKey;
+ protected boolean inheritVariables;
protected boolean fallbackToDefaultTenant;
protected String caseInstanceIdVariableName;
@@ -71,6 +72,14 @@ public void setInheritBusinessKey(boolean inheritBusinessKey) {
this.inheritBusinessKey = inheritBusinessKey;
}
+ public boolean isInheritVariables() {
+ return inheritVariables;
+ }
+
+ public void setInheritVariables(boolean inheritVariables) {
+ this.inheritVariables = inheritVariables;
+ }
+
public boolean isFallbackToDefaultTenant() {
return fallbackToDefaultTenant;
}
@@ -131,6 +140,7 @@ public void setValues(CaseServiceTask otherElement) {
setCaseInstanceName(otherElement.getCaseInstanceName());
setBusinessKey(otherElement.getBusinessKey());
setInheritBusinessKey(otherElement.isInheritBusinessKey());
+ setInheritVariables(otherElement.isInheritVariables());
setSameDeployment(otherElement.isSameDeployment());
setFallbackToDefaultTenant(otherElement.isFallbackToDefaultTenant());
setCaseInstanceIdVariableName(otherElement.getCaseInstanceIdVariableName());
diff --git a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java
index 9445e972b79..f373e9f0c7a 100644
--- a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java
+++ b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java
@@ -53,7 +53,7 @@ public String generateNewCaseInstanceId() {
@Override
public String startCaseInstance(String caseDefinitionId, String predefinedCaseInstanceId, String caseInstanceName, String businessKey,
- String executionId, String tenantId, Map inParametersMap) {
+ String executionId, String tenantId, Map inParametersMap, Map transientVariablesMap) {
CaseInstanceBuilder caseInstanceBuilder = cmmnEngineConfiguration.getCmmnRuntimeService().createCaseInstanceBuilder();
caseInstanceBuilder.caseDefinitionId(caseDefinitionId);
@@ -76,6 +76,10 @@ public String startCaseInstance(String caseDefinitionId, String predefinedCaseIn
caseInstanceBuilder.variable(target, inParametersMap.get(target));
}
+ if (transientVariablesMap != null && !transientVariablesMap.isEmpty()) {
+ caseInstanceBuilder.transientVariables(transientVariablesMap);
+ }
+
if (businessKey != null) {
caseInstanceBuilder.businessKey(businessKey);
}
diff --git a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/process/DefaultProcessInstanceService.java b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/process/DefaultProcessInstanceService.java
index a2e6f3fb44b..f84e21cc00f 100644
--- a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/process/DefaultProcessInstanceService.java
+++ b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/process/DefaultProcessInstanceService.java
@@ -60,16 +60,16 @@ public String generateNewProcessInstanceId() {
@Override
public String startProcessInstance(String processDefinitionId, String predefinedProcessInstanceId, String stageInstanceId,
- String tenantId, Map inParametersMap, String businessKey,
+ String tenantId, Map inParametersMap, Map transientVariablesMap, String businessKey,
Map variableFormVariables, FormInfo variableFormInfo, String variableFormOutcome) {
return startProcessInstance(processDefinitionId, predefinedProcessInstanceId, null, stageInstanceId, tenantId,
- inParametersMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
+ inParametersMap, transientVariablesMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
}
@Override
public String startProcessInstance(String processDefinitionId, String predefinedProcessInstanceId, String planItemInstanceId, String stageInstanceId,
- String tenantId, Map inParametersMap, String businessKey,
+ String tenantId, Map inParametersMap, Map transientVariablesMap, String businessKey,
Map variableFormVariables, FormInfo variableFormInfo, String variableFormOutcome) {
ProcessInstanceBuilder processInstanceBuilder = processEngineConfiguration.getRuntimeService().createProcessInstanceBuilder();
@@ -91,6 +91,10 @@ public String startProcessInstance(String processDefinitionId, String predefined
processInstanceBuilder.variable(target, inParametersMap.get(target));
}
+ if (transientVariablesMap != null && !transientVariablesMap.isEmpty()) {
+ processInstanceBuilder.transientVariables(transientVariablesMap);
+ }
+
if (businessKey != null) {
processInstanceBuilder.businessKey(businessKey);
}
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java
index 0829cb8c595..e7ed1452b8f 100644
--- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java
+++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java
@@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -1603,4 +1604,90 @@ public void testParentProcessInstanceIdQuery() {
}
}
+ @Test
+ @CmmnDeployment
+ public void testCaseTaskInheritVariables() {
+ Deployment deployment = processEngineRepositoryService.createDeployment()
+ .addClasspathResource("org/flowable/cmmn/test/caseTaskInheritVariablesProcess.bpmn20.xml")
+ .deploy();
+
+ try {
+ ObjectNode jsonVariable = processEngineConfiguration.getObjectMapper().createObjectNode();
+ jsonVariable.put("field", "value");
+
+ Map variables = new HashMap<>();
+ variables.put("parentVar1", "hello");
+ variables.put("parentVar2", "world");
+ variables.put("parentVarJson", jsonVariable);
+ ProcessInstance processInstance = processEngineRuntimeService.createProcessInstanceBuilder()
+ .processDefinitionKey("inheritVariablesProcess")
+ .variables(variables)
+ .transientVariables(Collections.singletonMap("parentTransientVar", "temp"))
+ .start();
+
+ Execution caseTaskExecution = processEngineRuntimeService.createExecutionQuery()
+ .onlyChildExecutions()
+ .processInstanceId(processInstance.getId())
+ .activityId("caseServiceTask")
+ .singleResult();
+ assertThat(caseTaskExecution).isNotNull();
+
+ CaseInstance childCaseInstance = cmmnRuntimeService.createCaseInstanceQuery()
+ .caseInstanceCallbackId(caseTaskExecution.getId())
+ .caseInstanceCallbackType(CallbackTypes.EXECUTION_CHILD_CASE)
+ .singleResult();
+ assertThat(childCaseInstance).isNotNull();
+
+ // All parent process variables are inherited into the child case instance (like a call activity)
+ assertThat(cmmnRuntimeService.getVariable(childCaseInstance.getId(), "parentVar1")).isEqualTo("hello");
+
+ // JSON variables are inherited (deep copied, consistent with in parameter handling)
+ JsonNode childJson = (JsonNode) cmmnRuntimeService.getVariable(childCaseInstance.getId(), "parentVarJson");
+ assertThat(childJson.path("field").asString()).isEqualTo("value");
+
+ // An explicit in parameter takes precedence over the inherited variable
+ assertThat(cmmnRuntimeService.getVariable(childCaseInstance.getId(), "parentVar2")).isEqualTo("overridden");
+
+ // Transient variables are inherited as transient: available while the child starts (the child task
+ // name resolves the ${parentTransientVar} expression) but not persisted on the child.
+ assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(childCaseInstance.getId()).singleResult().getName()).isEqualTo("temp");
+ assertThat(cmmnRuntimeService.getVariable(childCaseInstance.getId(), "parentTransientVar")).isNull();
+
+ } finally {
+ processEngineRepositoryService.deleteDeployment(deployment.getId(), true);
+ }
+ }
+
+ @Test
+ @CmmnDeployment(resources = "org/flowable/cmmn/test/caseTaskInheritVariablesDisabledChildCase.cmmn")
+ public void testCaseTaskInheritVariablesDisabled() {
+ Deployment deployment = processEngineRepositoryService.createDeployment()
+ .addClasspathResource("org/flowable/cmmn/test/caseTaskInheritVariablesDisabledProcess.bpmn20.xml")
+ .deploy();
+
+ try {
+ ProcessInstance processInstance = processEngineRuntimeService.startProcessInstanceByKey(
+ "inheritVariablesDisabledProcess", Collections.singletonMap("parentVar1", "hello"));
+
+ Execution caseTaskExecution = processEngineRuntimeService.createExecutionQuery()
+ .onlyChildExecutions()
+ .processInstanceId(processInstance.getId())
+ .activityId("caseServiceTask")
+ .singleResult();
+ assertThat(caseTaskExecution).isNotNull();
+
+ CaseInstance childCaseInstance = cmmnRuntimeService.createCaseInstanceQuery()
+ .caseInstanceCallbackId(caseTaskExecution.getId())
+ .caseInstanceCallbackType(CallbackTypes.EXECUTION_CHILD_CASE)
+ .singleResult();
+ assertThat(childCaseInstance).isNotNull();
+
+ // Without inheritVariables, parent variables are not copied into the child case
+ assertThat(cmmnRuntimeService.getVariable(childCaseInstance.getId(), "parentVar1")).isNull();
+
+ } finally {
+ processEngineRepositoryService.deleteDeployment(deployment.getId(), true);
+ }
+ }
+
}
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
index d7edbbc7869..73a0db1c63e 100644
--- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
+++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
@@ -1017,13 +1017,19 @@ public void testIOParameterCombinations() {
@Test
@CmmnDeployment
- @org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/oneTaskProcess.bpmn20.xml")
+ @org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/inheritVariablesChildProcess.bpmn20.xml")
public void testProcessTaskInheritVariables() {
+ ObjectNode jsonVariable = cmmnEngineConfiguration.getObjectMapper().createObjectNode();
+ jsonVariable.put("field", "value");
+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
.caseDefinitionKey("myCase")
.variable("caseVariableA", "hello")
.variable("caseVariableB", "world")
.variable("caseVariableC", 42)
+ .variable("caseVariableJson", jsonVariable)
+ .transientVariable("caseTransientVariable", "temp")
+ .transientVariable("collisionVar", "fromTransient")
.start();
PlanItemInstance processTaskPlanItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
@@ -1038,8 +1044,41 @@ public void testProcessTaskInheritVariables() {
assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableA")).isEqualTo("hello");
assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableC")).isEqualTo(42);
+ // JSON variables are inherited (deep copied, consistent with in parameter handling)
+ JsonNode childJson = (JsonNode) processEngineRuntimeService.getVariable(processInstanceId, "caseVariableJson");
+ assertThat(childJson.path("field").asString()).isEqualTo("value");
+
// An explicit in parameter takes precedence over the inherited variable
assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableB")).isEqualTo("overridden");
+
+ // Transient variables are inherited as transient: available while the child starts (the child task
+ // name resolves the ${caseTransientVariable} expression) but not persisted on the child.
+ assertThat(processEngineTaskService.createTaskQuery().processInstanceId(processInstanceId).singleResult().getName()).isEqualTo("temp");
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseTransientVariable")).isNull();
+
+ // An explicit in parameter targeting the same name as an inherited transient variable wins on the
+ // persisted child: the inherited value goes to the (non-persisted) transient channel, so once the
+ // transient state is gone only the explicit in parameter's persistent value remains.
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "collisionVar")).isEqualTo("fromInParam");
+ }
+
+ @Test
+ @CmmnDeployment
+ @org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/inheritVariablesDisabledChildProcess.bpmn20.xml")
+ public void testProcessTaskInheritVariablesDisabled() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .start();
+
+ PlanItemInstance processTaskPlanItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
+ .caseInstanceId(caseInstance.getId())
+ .planItemDefinitionType(PlanItemDefinitionType.PROCESS_TASK)
+ .singleResult();
+ String processInstanceId = processTaskPlanItemInstance.getReferenceId();
+
+ // Without inheritVariables, parent variables are not copied into the child process
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableA")).isNull();
}
@Test
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/CaseTaskTest.testCaseTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/CaseTaskTest.testCaseTaskInheritVariables.cmmn
new file mode 100644
index 00000000000..990fadf1c96
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/CaseTaskTest.testCaseTaskInheritVariables.cmmn
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
index 7062df23cc3..0e498eb8abe 100644
--- a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
@@ -10,8 +10,9 @@
+
-
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariablesDisabled.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariablesDisabled.cmmn
new file mode 100644
index 00000000000..6a5c68fe917
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariablesDisabled.cmmn
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledChildCase.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledChildCase.cmmn
new file mode 100644
index 00000000000..3eaa074cd0f
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledChildCase.cmmn
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledProcess.bpmn20.xml
new file mode 100644
index 00000000000..854b413cbc5
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesDisabledProcess.bpmn20.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesProcess.bpmn20.xml
new file mode 100644
index 00000000000..25b4997dc23
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskInheritVariablesProcess.bpmn20.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesChildProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesChildProcess.bpmn20.xml
new file mode 100644
index 00000000000..afe25e504a1
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesChildProcess.bpmn20.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesDisabledChildProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesDisabledChildProcess.bpmn20.xml
new file mode 100644
index 00000000000..d465f4ee954
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/inheritVariablesDisabledChildProcess.bpmn20.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
index 0d1bcfeb442..ff7e1ce354f 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
@@ -96,7 +96,8 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt
}
Map finalVariableMap = new HashMap<>();
- handleInParameters(planItemInstanceEntity, cmmnEngineConfiguration, finalVariableMap, cmmnEngineConfiguration.getExpressionManager());
+ Map transientVariablesMap = new HashMap<>();
+ handleInParameters(planItemInstanceEntity, cmmnEngineConfiguration, finalVariableMap, transientVariablesMap, cmmnEngineConfiguration.getExpressionManager());
// Needed for the form field handler later
Map variablesFromFormSubmission = null;
@@ -123,6 +124,9 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt
caseInstanceBuilder.businessKey(getBusinessKey(cmmnEngineConfiguration, planItemInstanceEntity, caseTask));
caseInstanceBuilder.variables(finalVariableMap);
+ if (!transientVariablesMap.isEmpty()) {
+ caseInstanceBuilder.transientVariables(transientVariablesMap);
+ }
if (sameDeployment) {
caseInstanceBuilder.caseDefinitionParentDeploymentId(
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
index f9d12caf70e..1efe012c4cf 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
@@ -31,6 +31,7 @@
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.common.engine.impl.util.JsonUtil;
import org.flowable.form.api.FormInfo;
/**
@@ -88,12 +89,23 @@ public void trigger(CommandContext commandContext, PlanItemInstanceEntity planIt
protected void handleInParameters(PlanItemInstanceEntity planItemInstanceEntity,
CmmnEngineConfiguration cmmnEngineConfiguration, Map inParametersMap,
- ExpressionManager expressionManager) {
+ Map transientVariablesMap, ExpressionManager expressionManager) {
if (inheritVariables) {
// Inherit all variables from the parent. Explicit in parameters are
- // applied afterwards, so they take precedence over the inherited variables.
- inParametersMap.putAll(planItemInstanceEntity.getVariables());
+ // applied afterwards, so they take precedence over the inherited persistent variables.
+ // Transient variables are kept transient in the child so they are not persisted
+ // JSON variables are deep copied so the parent and child don't share the same instance
+ Map transientVariables = planItemInstanceEntity.getTransientVariables();
+ for (Map.Entry entry : planItemInstanceEntity.getVariables().entrySet()) {
+ String variableName = entry.getKey();
+ Object variableValue = JsonUtil.deepCopyIfJson(entry.getValue());
+ if (transientVariables.containsKey(variableName)) {
+ transientVariablesMap.put(variableName, variableValue);
+ } else {
+ inParametersMap.put(variableName, variableValue);
+ }
+ }
}
IOParameterUtil.processInParameters(inParameters, planItemInstanceEntity, inParametersMap, expressionManager);
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
index 7119c012ede..dd021545b14 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
@@ -88,7 +88,8 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt
}
Map inParametersMap = new HashMap<>();
- handleInParameters(planItemInstanceEntity, cmmnEngineConfiguration, inParametersMap, cmmnEngineConfiguration.getExpressionManager());
+ Map transientVariablesMap = new HashMap<>();
+ handleInParameters(planItemInstanceEntity, cmmnEngineConfiguration, inParametersMap, transientVariablesMap, cmmnEngineConfiguration.getExpressionManager());
FormInfo variableFormInfo = null;
Map variableFormVariables = null;
@@ -136,7 +137,7 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt
try {
processInstanceService.startProcessInstance(processDefinitionId, processInstanceId, planItemInstanceEntity.getId(), planItemInstanceEntity.getStageInstanceId(),
- planItemInstanceEntity.getTenantId(), inParametersMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
+ planItemInstanceEntity.getTenantId(), inParametersMap, transientVariablesMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
} catch (BusinessError businessError) {
// An uncaught BusinessError from the child BPMN process propagates as a fault on this plan item.
// Clean up the orphaned child process instance (consistent with BPMN ErrorPropagation.executeCatch
@@ -150,7 +151,7 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt
} else {
processInstanceService.startProcessInstance(processDefinitionId, processInstanceId, planItemInstanceEntity.getStageInstanceId(),
- planItemInstanceEntity.getTenantId(), inParametersMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
+ planItemInstanceEntity.getTenantId(), inParametersMap, transientVariablesMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
}
if (!blocking) {
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/process/ProcessInstanceService.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/process/ProcessInstanceService.java
index e12ea8b06a1..36dfd0a2190 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/process/ProcessInstanceService.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/process/ProcessInstanceService.java
@@ -35,14 +35,14 @@ public interface ProcessInstanceService {
* Starts a process instance without a reference to a plan item instance (i.e. non-blocking behavior).
*/
String startProcessInstance(String processDefinitionId, String predefinedProcessInstanceId, String stageInstanceId,
- String tenantId, Map inParametersMap, String businessKey,
+ String tenantId, Map inParametersMap, Map transientVariablesMap, String businessKey,
Map variableFormVariables, FormInfo variableFormInfo, String variableFormOutcome);
/**
* Starts a process instance with a reference to a plan item instance (i.e. blocking behavior).
*/
String startProcessInstance(String processDefinitionId, String predefinedProcessInstanceId, String planItemInstanceId, String stageInstanceId,
- String tenantId, Map inParametersMap, String businessKey,
+ String tenantId, Map inParametersMap, Map transientVariablesMap, String businessKey,
Map variableFormVariables, FormInfo variableFormInfo, String variableFormOutcome);
/**
diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
index bd24368a082..a2669fa121c 100644
--- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
+++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
@@ -1239,18 +1239,23 @@ public void testWithInheritBusinessKeyButWithoutBusinessKey() {
@Test
@CmmnDeployment(resources = {
"org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn",
- "org/flowable/cmmn/test/runtime/oneTaskCase.cmmn"
+ "org/flowable/cmmn/test/runtime/CaseTaskTest.inheritVariablesChildCase.cmmn"
})
public void testCaseTaskInheritVariables() {
+ ObjectNode jsonVariable = cmmnEngineConfiguration.getObjectMapper().createObjectNode();
+ jsonVariable.put("field", "value");
+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
.caseDefinitionKey("myCase")
.variable("caseVariableA", "hello")
.variable("caseVariableB", "world")
.variable("caseVariableC", 42)
+ .variable("caseVariableJson", jsonVariable)
+ .transientVariable("caseTransientVariable", "temp")
.start();
CaseInstance subCase = cmmnRuntimeService.createCaseInstanceQuery()
- .caseDefinitionKey("oneTaskCase")
+ .caseDefinitionKey("inheritVariablesChildCase")
.singleResult();
assertThat(subCase).isNotNull();
@@ -1258,8 +1263,37 @@ public void testCaseTaskInheritVariables() {
assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableA")).isEqualTo("hello");
assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableC")).isEqualTo(42);
+ // JSON variables are inherited (deep copied, consistent with in parameter handling)
+ JsonNode childJson = (JsonNode) cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableJson");
+ assertThat(childJson.path("field").asString()).isEqualTo("value");
+
// An explicit in parameter takes precedence over the inherited variable
assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableB")).isEqualTo("overridden");
+
+ // Transient variables are inherited as transient: available while the child starts (the child task
+ // name resolves the ${caseTransientVariable} expression) but not persisted on the child.
+ assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(subCase.getId()).singleResult().getName()).isEqualTo("temp");
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseTransientVariable")).isNull();
+ }
+
+ @Test
+ @CmmnDeployment(resources = {
+ "org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariablesDisabled.cmmn",
+ "org/flowable/cmmn/test/runtime/oneTaskCase.cmmn"
+ })
+ public void testCaseTaskInheritVariablesDisabled() {
+ cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .start();
+
+ CaseInstance subCase = cmmnRuntimeService.createCaseInstanceQuery()
+ .caseDefinitionKey("oneTaskCase")
+ .singleResult();
+ assertThat(subCase).isNotNull();
+
+ // Without inheritVariables, parent variables are not copied into the child case
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableA")).isNull();
}
@Test
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.inheritVariablesChildCase.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.inheritVariablesChildCase.cmmn
new file mode 100644
index 00000000000..b96d04a3728
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.inheritVariablesChildCase.cmmn
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
index adaeedb0ffd..492f03f9660 100644
--- a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
@@ -6,7 +6,7 @@
-
+
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariablesDisabled.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariablesDisabled.cmmn
new file mode 100644
index 00000000000..f9bbb4c11ef
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariablesDisabled.cmmn
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CallActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CallActivityBehavior.java
index 0a1f1710911..6d848fe4338 100644
--- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CallActivityBehavior.java
+++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CallActivityBehavior.java
@@ -36,6 +36,7 @@
import org.flowable.common.engine.api.scope.ScopeTypes;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.common.engine.impl.util.JsonUtil;
import org.flowable.engine.DynamicBpmnConstants;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.delegate.DelegateExecution;
@@ -160,11 +161,13 @@ public void execute(DelegateExecution execution) {
// The executionVariables contain all variables, including the transient variables.
// Hence why that map is iterated and the transient variables are split off
String variableName = entry.getKey();
+ // JSON variables are deep copied so the parent and child don't share the same instance
+ Object variableValue = JsonUtil.deepCopyIfJson(entry.getValue());
if (transientVariables.containsKey(variableName)) {
- instanceBeforeContext.getTransientVariables().put(variableName, entry.getValue());
+ instanceBeforeContext.getTransientVariables().put(variableName, variableValue);
} else {
- instanceBeforeContext.getVariables().put(variableName, entry.getValue());
+ instanceBeforeContext.getVariables().put(variableName, variableValue);
}
}
diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java
index 6e4ff39a68e..ec77c2ada54 100644
--- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java
+++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java
@@ -26,6 +26,7 @@
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.common.engine.impl.util.JsonUtil;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.helper.ErrorPropagation;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
@@ -86,6 +87,24 @@ public void execute(DelegateExecution execution) {
}
Map inParameters = new HashMap<>();
+ Map transientVariables = new HashMap<>();
+
+ if (caseServiceTask.isInheritVariables()) {
+ // Inherit all variables from the parent. Explicit in parameters are
+ // applied afterwards, so they take precedence over the inherited persistent variables.
+ // Transient variables are kept transient in the child so they are not persisted
+ // JSON variables are deep copied so the parent and child don't share the same instance
+ Map executionTransientVariables = execution.getTransientVariables();
+ for (Map.Entry entry : execution.getVariables().entrySet()) {
+ String variableName = entry.getKey();
+ Object variableValue = JsonUtil.deepCopyIfJson(entry.getValue());
+ if (executionTransientVariables.containsKey(variableName)) {
+ transientVariables.put(variableName, variableValue);
+ } else {
+ inParameters.put(variableName, variableValue);
+ }
+ }
+ }
// copy process variables
IOParameterUtil.processInParameters(caseServiceTask.getInParameters(), execution, inParameters::put, inParameters::put, expressionManager);
@@ -120,7 +139,7 @@ public void execute(DelegateExecution execution) {
try {
caseInstanceService.startCaseInstance(caseDefinitionId, caseInstanceId,
- caseInstanceName, businessKey, execution.getId(), execution.getTenantId(), inParameters);
+ caseInstanceName, businessKey, execution.getId(), execution.getTenantId(), inParameters, transientVariables);
} catch (BusinessError businessError) {
// An uncaught BusinessError from the child CMMN case propagates as a BPMN error.
// Clean up the orphaned child case instance, then propagate.
diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmmn/CaseInstanceService.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmmn/CaseInstanceService.java
index 9d64a11957b..9ac191a8149 100644
--- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmmn/CaseInstanceService.java
+++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmmn/CaseInstanceService.java
@@ -29,7 +29,7 @@ public interface CaseInstanceService {
String generateNewCaseInstanceId();
String startCaseInstance(String caseDefinitionId, String predefinedCaseInstanceId, String caseInstanceName, String businessKey,
- String executionId, String tenantId, Map inParametersMap);
+ String executionId, String tenantId, Map inParametersMap, Map transientVariablesMap);
void handleSignalEvent(EventSubscriptionEntity eventSubscription, Map variables);