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-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/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 aa6f07e8267..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 @@ -1015,6 +1015,72 @@ 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/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() + .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); + + // 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 @CmmnDeployment public void testProcessTaskWithSkipExpressions() { 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 new file mode 100644 index 00000000000..0e498eb8abe --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + 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 f62dbd9eb36..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 @@ -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(); @@ -95,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; @@ -122,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 866bc0edd00..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; /** @@ -43,6 +44,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 +52,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 @@ -81,7 +89,24 @@ 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 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 c47331e67fa..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 @@ -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(); @@ -87,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; @@ -135,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 @@ -149,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 8ff291a8626..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 @@ -1236,6 +1236,66 @@ public void testWithInheritBusinessKeyButWithoutBusinessKey() { cmmnRuntimeService.triggerPlanItemInstance(planItemInstance.getId()); } + @Test + @CmmnDeployment(resources = { + "org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.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("inheritVariablesChildCase") + .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); + + // 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 @CmmnDeployment public void testIdVariableName() { 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 new file mode 100644 index 00000000000..492f03f9660 --- /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-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-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; 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);