diff --git a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/CaseInstance.xml b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/CaseInstance.xml index dcead35b4cf..031169cc42f 100644 --- a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/CaseInstance.xml +++ b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/CaseInstance.xml @@ -229,7 +229,7 @@ - @@ -241,6 +241,7 @@ + diff --git a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricCaseInstance.xml b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricCaseInstance.xml index 7e2109e9b88..56d2b2e3b6f 100644 --- a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricCaseInstance.xml +++ b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricCaseInstance.xml @@ -218,7 +218,7 @@ - @@ -230,6 +230,7 @@ + diff --git a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricPlanItemInstance.xml b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricPlanItemInstance.xml index 500bec2d9ec..e678413a1d6 100644 --- a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricPlanItemInstance.xml +++ b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/HistoricPlanItemInstance.xml @@ -331,7 +331,7 @@ - @@ -343,6 +343,7 @@ + diff --git a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/PlanItemInstance.xml b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/PlanItemInstance.xml index 9ab997353f3..c4d3746be92 100644 --- a/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/PlanItemInstance.xml +++ b/modules/flowable-cmmn-engine/src/main/resources/org/flowable/cmmn/db/mapping/entity/PlanItemInstance.xml @@ -330,7 +330,7 @@ - @@ -342,6 +342,7 @@ + diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricCaseInstanceQueryImplTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricCaseInstanceQueryImplTest.java index 43bf49ad152..4f7462aca59 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricCaseInstanceQueryImplTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricCaseInstanceQueryImplTest.java @@ -31,6 +31,7 @@ import org.flowable.cmmn.api.runtime.CaseInstanceState; import org.flowable.cmmn.api.runtime.PlanItemInstance; import org.flowable.cmmn.engine.CaseLocalizationManager; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricCaseInstanceEntity; import org.flowable.cmmn.engine.impl.runtime.CaseInstanceQueryImpl; import org.flowable.cmmn.engine.test.CmmnDeployment; import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; @@ -39,6 +40,7 @@ import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.identitylink.api.IdentityLinkType; import org.flowable.task.api.Task; +import org.flowable.variable.api.history.HistoricVariableInstance; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1954,6 +1956,17 @@ public void testQueryCaseInstanceWithVariablesReturnsCaseDefinitionInformation() assertThat(historicCaseInstance.getCaseVariables()).containsOnly( entry("stringVar", "test") ); + assertThat(((HistoricCaseInstanceEntity) historicCaseInstance).getQueryVariables()) + .extracting(HistoricVariableInstance::getScopeDefinitionId) + .containsExactly(historicCaseInstance.getCaseDefinitionId()); + + String caseInstanceWithoutVariablesId = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("oneTaskCase").start().getId(); + historicCaseInstance = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstanceWithoutVariablesId) + .includeCaseVariables() + .singleResult(); + assertThat(historicCaseInstance.getCaseVariables()).isEmpty(); + assertThat(((HistoricCaseInstanceEntity) historicCaseInstance).getQueryVariables()).isEmpty(); } } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricPlanItemInstanceQueryTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricPlanItemInstanceQueryTest.java index e18fd0b8f22..0e240afcaad 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricPlanItemInstanceQueryTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/HistoricPlanItemInstanceQueryTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; import java.util.ArrayList; import java.util.Arrays; @@ -30,12 +31,16 @@ import org.flowable.cmmn.api.runtime.PlanItemInstance; import org.flowable.cmmn.api.runtime.PlanItemInstanceState; import org.flowable.cmmn.engine.PlanItemLocalizationManager; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricPlanItemInstanceEntity; import org.flowable.cmmn.engine.test.CmmnDeployment; import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; import org.flowable.cmmn.test.FlowableCmmnTestCase; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.impl.history.HistoryLevel; import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity; +import org.flowable.variable.api.history.HistoricVariableInstance; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -359,6 +364,16 @@ public void testIncludeLocalVariables() { .singleResult(); cmmnTaskService.complete(task.getId()); + HistoricTaskInstance historicTask = cmmnHistoryService.createHistoricTaskInstanceQuery() + .taskId(task.getId()) + .includeCaseVariables() + .includeTaskLocalVariables() + .singleResult(); + assertThat(((HistoricTaskInstanceEntity) historicTask).getQueryVariables()) + .extracting(HistoricVariableInstance::getScopeDefinitionId, HistoricVariableInstance::getProcessDefinitionId) + .isNotEmpty() + .containsOnly(tuple(historicTask.getScopeDefinitionId(), null)); + HistoricPlanItemInstance planItemInstance = cmmnHistoryService.createHistoricPlanItemInstanceQuery() .planItemInstanceId(planItemInstances.get(0).getId()).singleResult(); assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).isEmpty(); @@ -370,6 +385,14 @@ public void testIncludeLocalVariables() { assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).containsOnly( entry("localVar", "someValue") ); + assertThat(((HistoricPlanItemInstanceEntity) planItemInstance).getQueryVariables()) + .extracting(HistoricVariableInstance::getScopeDefinitionId) + .containsExactly(planItemInstance.getCaseDefinitionId()); + + planItemInstance = cmmnHistoryService.createHistoricPlanItemInstanceQuery().planItemInstanceId(planItemInstances.get(1).getId()).includeLocalVariables() + .singleResult(); + assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).isEmpty(); + assertThat(((HistoricPlanItemInstanceEntity) planItemInstance).getQueryVariables()).isEmpty(); } @Test diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java index 7fe4a27a470..75d3a14ddeb 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java @@ -50,6 +50,7 @@ import org.flowable.common.engine.impl.interceptor.CommandExecutor; import org.flowable.identitylink.api.IdentityLinkType; import org.flowable.task.api.Task; +import org.flowable.variable.api.persistence.entity.VariableInstance; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1968,6 +1969,17 @@ public void testQueryCaseInstanceWithVariablesReturnsCaseDefinitionInformation() assertThat(caseInstance.getCaseVariables()).containsOnly( entry("stringVar","test") ); + assertThat(((CaseInstanceEntity) caseInstance).getQueryVariables()) + .extracting(VariableInstance::getScopeDefinitionId) + .containsExactly(caseInstance.getCaseDefinitionId()); + + String caseInstanceWithoutVariablesId = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("oneTaskCase").start().getId(); + caseInstance = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstanceWithoutVariablesId) + .includeCaseVariables() + .singleResult(); + assertThat(caseInstance.getCaseVariables()).isEmpty(); + assertThat(((CaseInstanceEntity) caseInstance).getQueryVariables()).isEmpty(); } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/PlanItemInstanceQueryTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/PlanItemInstanceQueryTest.java index 89d713dfa47..0fb36da6d45 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/PlanItemInstanceQueryTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/PlanItemInstanceQueryTest.java @@ -30,9 +30,12 @@ import org.flowable.cmmn.api.runtime.PlanItemInstanceState; import org.flowable.cmmn.api.runtime.UserEventListenerInstance; import org.flowable.cmmn.engine.PlanItemLocalizationManager; +import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity; import org.flowable.cmmn.engine.test.CmmnDeployment; import org.flowable.cmmn.test.FlowableCmmnTestCase; import org.flowable.task.api.Task; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.flowable.variable.api.persistence.entity.VariableInstance; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1638,6 +1641,10 @@ public void testIncludeLocalVariables() { .includeCaseVariables() .includeTaskLocalVariables() .singleResult(); + assertThat(((TaskEntity) task).getQueryVariables()) + .extracting(VariableInstance::getScopeDefinitionId, VariableInstance::getProcessDefinitionId) + .isNotEmpty() + .containsOnly(tuple(task.getScopeDefinitionId(), null)); PlanItemInstance planItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery().planItemInstanceId(planItemInstances.get(0).getId()).singleResult(); assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).isEmpty(); @@ -1647,6 +1654,13 @@ public void testIncludeLocalVariables() { assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).containsOnly( entry("localVar", "someValue") ); + assertThat(((PlanItemInstanceEntity) planItemInstance).getQueryVariables()) + .extracting(VariableInstance::getScopeDefinitionId) + .containsExactly(planItemInstance.getCaseDefinitionId()); + + planItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery().planItemInstanceId(planItemInstances.get(1).getId()).includeLocalVariables().singleResult(); + assertThat(planItemInstance.getPlanItemInstanceLocalVariables()).isEmpty(); + assertThat(((PlanItemInstanceEntity) planItemInstance).getQueryVariables()).isEmpty(); } @Test diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java index 3ad2406c0b4..d383045dc33 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java @@ -408,6 +408,44 @@ public void testHistoricVariables() { } } + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/one-human-task-model.cmmn") + public void testHistoricVariableCreatedAndUpdatedFromRuntimeVariable() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("oneTaskCase").start(); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + HistoricVariableInstance createdVariable = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> { + cmmnRuntimeService.setVariable(caseInstance.getId(), "customer", "Kermit"); + return getHistoricVariableInstance(cmmnRuntimeService.getVariableInstance(caseInstance.getId(), "customer").getId()); + }); + assertThat(createdVariable) + .extracting(HistoricVariableInstance::getValue, HistoricVariableInstance::getScopeDefinitionId) + .containsExactly("Kermit", caseInstance.getCaseDefinitionId()); + + HistoricVariableInstance updatedVariable = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> { + cmmnRuntimeService.setVariable(caseInstance.getId(), "customer", "Gonzo"); + return getHistoricVariableInstance(cmmnRuntimeService.getVariableInstance(caseInstance.getId(), "customer").getId()); + }); + assertThat(updatedVariable) + .extracting(HistoricVariableInstance::getValue, HistoricVariableInstance::getScopeDefinitionId) + .containsExactly("Gonzo", caseInstance.getCaseDefinitionId()); + + String taskId = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult().getId(); + HistoricVariableInstance createdTaskVariable = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> { + cmmnTaskService.setVariableLocal(taskId, "taskCustomer", "Kermit"); + return getHistoricVariableInstance(cmmnTaskService.getVariableInstanceLocal(taskId, "taskCustomer").getId()); + }); + assertThat(createdTaskVariable) + .extracting(HistoricVariableInstance::getValue, HistoricVariableInstance::getScopeDefinitionId) + .containsExactly("Kermit", caseInstance.getCaseDefinitionId()); + } + } + + protected HistoricVariableInstance getHistoricVariableInstance(String variableId) { + // Called in the command that created or updated the variable, so the historic variable is the one from the entity cache + return cmmnEngineConfiguration.getVariableServiceConfiguration().getHistoricVariableService().getHistoricVariableInstance(variableId); + } + @Test @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn") public void testVariableInstanceQueryByCaseInstanceIds() { diff --git a/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/Execution.xml b/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/Execution.xml index 291d52fdd4e..01414d7d1ea 100644 --- a/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/Execution.xml +++ b/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/Execution.xml @@ -543,12 +543,13 @@ - + + diff --git a/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/HistoricProcessInstance.xml b/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/HistoricProcessInstance.xml index d259d25f1e9..2f04a453e02 100644 --- a/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/HistoricProcessInstance.xml +++ b/modules/flowable-engine/src/main/resources/org/flowable/db/mapping/entity/HistoricProcessInstance.xml @@ -362,12 +362,13 @@ - + + diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricProcessInstanceAndVariablesQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricProcessInstanceAndVariablesQueryTest.java index 6e17f5a8ac1..b314dfa20ba 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricProcessInstanceAndVariablesQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricProcessInstanceAndVariablesQueryTest.java @@ -24,11 +24,13 @@ import org.flowable.common.engine.impl.history.HistoryLevel; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntity; import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; +import org.flowable.variable.api.history.HistoricVariableInstance; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -90,6 +92,9 @@ public void testQuery() { Map variableMap = processInstance.getProcessVariables(); assertThat(variableMap) .containsExactly(entry("anothertest", 123)); + assertThat(((HistoricProcessInstanceEntity) processInstance).getQueryVariables()) + .extracting(HistoricVariableInstance::getProcessDefinitionId) + .containsExactly(processInstance.getProcessDefinitionId()); List instanceList = historyService.createHistoricProcessInstanceQuery().includeProcessVariables().list(); assertThat(instanceList).hasSize(6); @@ -202,6 +207,12 @@ public void testQuery() { instanceList = historyService.createHistoricProcessInstanceQuery().variableValueEquals("test", "test").includeProcessVariables() .orderByProcessInstanceId().asc().listPage(0, 50); assertThat(instanceList).hasSize(4); + + String processInstanceWithoutVariablesId = runtimeService.startProcessInstanceByKey(PROCESS_DEFINITION_KEY_2).getId(); + processInstance = historyService.createHistoricProcessInstanceQuery().includeProcessVariables().processInstanceId(processInstanceWithoutVariablesId) + .singleResult(); + assertThat(processInstance.getProcessVariables()).isEmpty(); + assertThat(((HistoricProcessInstanceEntity) processInstance).getQueryVariables()).isEmpty(); } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricTaskAndVariablesQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricTaskAndVariablesQueryTest.java index 7b396365761..57a08a1b532 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricTaskAndVariablesQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoricTaskAndVariablesQueryTest.java @@ -35,6 +35,8 @@ import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity; +import org.flowable.variable.api.history.HistoricVariableInstance; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -173,6 +175,10 @@ public void testQuery() { entry("processVar", true), entry("anotherProcessVar", 123) ); + assertThat(((HistoricTaskInstanceEntity) task).getQueryVariables()) + .extracting(HistoricVariableInstance::getProcessDefinitionId, HistoricVariableInstance::getScopeDefinitionId) + .hasSize(3) + .containsOnly(tuple(task.getProcessDefinitionId(), null)); task = historyService.createHistoricTaskInstanceQuery().taskAssignee("gonzo").singleResult(); taskService.complete(task.getId()); @@ -197,6 +203,12 @@ public void testQuery() { .or().taskInvolvedGroups(Collections.singleton("testGroup")).taskInvolvedUser("kermit").endOr().count()).isEqualTo(3); assertThat(historyService.createHistoricTaskInstanceQuery() .or().taskInvolvedGroups(Collections.singleton("testGroup")).processInstanceId("undefined").endOr().count()).isEqualTo(1); + + String processInstanceWithoutVariablesId = runtimeService.startProcessInstanceByKey("oneTaskProcess").getId(); + task = historyService.createHistoricTaskInstanceQuery().includeTaskLocalVariables().includeProcessVariables() + .processInstanceId(processInstanceWithoutVariablesId).singleResult(); + assertThat(task.getProcessVariables()).isEmpty(); + assertThat(((HistoricTaskInstanceEntity) task).getQueryVariables()).isEmpty(); } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/ProcessInstanceAndVariablesQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/ProcessInstanceAndVariablesQueryTest.java index 807c819b20c..813afa27bd9 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/ProcessInstanceAndVariablesQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/ProcessInstanceAndVariablesQueryTest.java @@ -19,9 +19,11 @@ import java.util.List; import java.util.Map; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstanceQuery; +import org.flowable.variable.api.persistence.entity.VariableInstance; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -82,6 +84,9 @@ public void testQuery() { Map variableMap = processInstance.getProcessVariables(); assertThat(variableMap) .containsOnly(entry("anothertest", 123)); + assertThat(((ExecutionEntity) processInstance).getQueryVariables()) + .extracting(VariableInstance::getProcessDefinitionId) + .containsExactly(processInstance.getProcessDefinitionId()); List instanceList = runtimeService.createProcessInstanceQuery().includeProcessVariables().list(); assertThat(instanceList).hasSize(7); @@ -154,6 +159,12 @@ public void testQuery() { variableMap = processInstance.getProcessVariables(); assertThat(variableMap) .containsOnly(entry("test4", "test4")); + + String processInstanceWithoutVariablesId = runtimeService.startProcessInstanceByKey(PROCESS_DEFINITION_KEY_2).getId(); + processInstance = runtimeService.createProcessInstanceQuery().includeProcessVariables().processInstanceId(processInstanceWithoutVariablesId) + .singleResult(); + assertThat(processInstance.getProcessVariables()).isEmpty(); + assertThat(((ExecutionEntity) processInstance).getQueryVariables()).isEmpty(); } @Test diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/task/TaskAndVariablesQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/task/TaskAndVariablesQueryTest.java index 152d60ede47..fa914f9a87c 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/task/TaskAndVariablesQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/task/TaskAndVariablesQueryTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -33,6 +34,7 @@ import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.flowable.variable.api.persistence.entity.VariableInstance; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -169,6 +171,16 @@ public void testQuery() { entry("anotherProcessVar", 123) ); assertThat(new String((byte[]) task.getProcessVariables().get("binaryVariable"))).isEqualTo("This is a binary process variable"); + assertThat(((TaskEntity) task).getQueryVariables()) + .extracting(VariableInstance::getProcessDefinitionId, VariableInstance::getScopeDefinitionId) + .hasSize(4) + .containsOnly(tuple(task.getProcessDefinitionId(), null)); + + String processInstanceWithoutVariablesId = runtimeService.startProcessInstanceByKey("oneTaskProcess").getId(); + task = taskService.createTaskQuery().includeTaskLocalVariables().includeProcessVariables().processInstanceId(processInstanceWithoutVariablesId) + .singleResult(); + assertThat(task.getProcessVariables()).isEmpty(); + assertThat(((TaskEntity) task).getQueryVariables()).isEmpty(); } @Test diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/history/HistoricVariableInstanceTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/history/HistoricVariableInstanceTest.java index badd0c5f20b..d4169aa403b 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/history/HistoricVariableInstanceTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/history/HistoricVariableInstanceTest.java @@ -891,4 +891,34 @@ public void testQueryVariableValueEqualsAndNotEquals() { .extracting(HistoricVariableInstance::getProcessInstanceId) .isEmpty(); } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/oneTaskProcess.bpmn20.xml") + public void testHistoricVariableCreatedAndUpdatedFromRuntimeVariable() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, processEngineConfiguration)) { + HistoricVariableInstance createdVariable = managementService.executeCommand(commandContext -> { + runtimeService.setVariable(processInstance.getId(), "customer", "Kermit"); + return getHistoricVariableInstance(processInstance.getId(), "customer"); + }); + assertThat(createdVariable) + .extracting(HistoricVariableInstance::getValue, HistoricVariableInstance::getProcessDefinitionId) + .containsExactly("Kermit", processInstance.getProcessDefinitionId()); + + HistoricVariableInstance updatedVariable = managementService.executeCommand(commandContext -> { + runtimeService.setVariable(processInstance.getId(), "customer", "Gonzo"); + return getHistoricVariableInstance(processInstance.getId(), "customer"); + }); + assertThat(updatedVariable) + .extracting(HistoricVariableInstance::getValue, HistoricVariableInstance::getProcessDefinitionId) + .containsExactly("Gonzo", processInstance.getProcessDefinitionId()); + } + } + + protected HistoricVariableInstance getHistoricVariableInstance(String processInstanceId, String variableName) { + // Called in the command that created or updated the variable, so the historic variable is the one from the entity cache + String variableId = runtimeService.getVariableInstance(processInstanceId, variableName).getId(); + return processEngineConfiguration.getVariableServiceConfiguration().getHistoricVariableService().getHistoricVariableInstance(variableId); + } } diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java index 1d32754de8f..1a290fae72d 100644 --- a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java @@ -2508,6 +2508,10 @@ public boolean isIncludeProcessVariables() { return includeProcessVariables; } + public boolean isIncludeCaseVariables() { + return includeCaseVariables; + } + public boolean isExcludeVariableInitialization() { return excludeVariableInitialization; } diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/TaskEntityImpl.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/TaskEntityImpl.java index 89e4b1f9790..8ba65917c7a 100644 --- a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/TaskEntityImpl.java +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/TaskEntityImpl.java @@ -237,6 +237,7 @@ protected void initializeVariableInstanceBackPointer(VariableInstance variableIn variableInstance.setScopeId(this.scopeId); variableInstance.setScopeType(this.scopeType); variableInstance.setSubScopeId(this.subScopeId); + variableInstance.setScopeDefinitionId(this.scopeDefinitionId); } else { variableInstance.setExecutionId(this.executionId); variableInstance.setProcessInstanceId(this.processInstanceId); diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisHistoricTaskInstanceDataManager.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisHistoricTaskInstanceDataManager.java index 7a33cc85d92..d25c5474a83 100644 --- a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisHistoricTaskInstanceDataManager.java +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisHistoricTaskInstanceDataManager.java @@ -103,8 +103,15 @@ public List findHistoricTaskInstancesByQueryCriteria(Histo @Override @SuppressWarnings("unchecked") public List findHistoricTaskInstancesAndRelatedEntitiesByQueryCriteria(HistoricTaskInstanceQueryImpl historicTaskInstanceQuery) { + final String query; + if (historicTaskInstanceQuery.isIncludeTaskLocalVariables() || historicTaskInstanceQuery.isIncludeProcessVariables() + || historicTaskInstanceQuery.isIncludeCaseVariables()) { + query = "selectHistoricTaskInstancesWithRelatedEntitiesByQueryCriteria"; + } else { + query = "selectHistoricTaskInstancesWithIdentityLinksByQueryCriteria"; + } setSafeInValueLists(historicTaskInstanceQuery); - return getDbSqlSession().selectList("selectHistoricTaskInstancesWithRelatedEntitiesByQueryCriteria", historicTaskInstanceQuery, getManagedEntityClass()); + return getDbSqlSession().selectList(query, historicTaskInstanceQuery, getManagedEntityClass()); } @Override diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisTaskDataManager.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisTaskDataManager.java index e195f9146b7..3f546a482ca 100644 --- a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisTaskDataManager.java +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/persistence/entity/data/impl/MybatisTaskDataManager.java @@ -112,7 +112,12 @@ public List findTasksByQueryCriteria(TaskQueryImpl taskQuery) { @Override @SuppressWarnings("unchecked") public List findTasksWithRelatedEntitiesByQueryCriteria(TaskQueryImpl taskQuery) { - final String query = "selectTasksWithRelatedEntitiesByQueryCriteria"; + final String query; + if (taskQuery.isIncludeTaskLocalVariables() || taskQuery.isIncludeProcessVariables() || taskQuery.isIncludeCaseVariables()) { + query = "selectTasksWithRelatedEntitiesByQueryCriteria"; + } else { + query = "selectTasksWithIdentityLinksByQueryCriteria"; + } setSafeInValueLists(taskQuery); return getDbSqlSession().selectList(query, taskQuery, getManagedEntityClass()); } diff --git a/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/HistoricTaskInstance.xml b/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/HistoricTaskInstance.xml index 0bfd6acdaa6..ff1c7b10473 100644 --- a/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/HistoricTaskInstance.xml +++ b/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/HistoricTaskInstance.xml @@ -465,7 +465,7 @@ - + @@ -502,12 +502,27 @@ - + + + + + + + + + + + + + + + + @@ -520,15 +535,6 @@ - - - - - - - - - @@ -606,6 +612,14 @@ + + + + select RES.*, VAR.ID_ as VAR_ID_, VAR.NAME_ as VAR_NAME_, VAR.VAR_TYPE_ as VAR_TYPE_, VAR.REV_ as VAR_REV_, @@ -645,7 +659,7 @@ left outer join ${prefix}ACT_HI_IDENTITYLINK ILINK on RES.ID_ = ILINK.TASK_ID_ ${outerJoinOrderBy} - + delete from ${prefix}ACT_HI_TASKINST diff --git a/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/Task.xml b/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/Task.xml index 673a26fb924..b8534188231 100644 --- a/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/Task.xml +++ b/modules/flowable-task-service/src/main/resources/org/flowable/task/service/db/mapping/entity/Task.xml @@ -370,7 +370,7 @@ - + @@ -409,12 +409,27 @@ - + + + + + + + + + + + + + + + + @@ -427,15 +442,6 @@ - - - - - - - - - @@ -490,8 +496,16 @@ - + + + + + SELECT RES.*, VAR.ID_ as VAR_ID_, VAR.NAME_ as VAR_NAME_, VAR.TYPE_ as VAR_TYPE_, VAR.REV_ as VAR_REV_, @@ -530,7 +544,7 @@ left outer join ${prefix}ACT_RU_IDENTITYLINK ILINK on RES.ID_ = ILINK.TASK_ID_ ${outerJoinOrderBy} - + diff --git a/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/history/HistoricVariableInstance.java b/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/history/HistoricVariableInstance.java index e1f113aeb87..58e6f9eada4 100644 --- a/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/history/HistoricVariableInstance.java +++ b/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/history/HistoricVariableInstance.java @@ -38,6 +38,12 @@ public interface HistoricVariableInstance extends HistoricData { /** The process instance reference. */ String getProcessInstanceId(); + /** + * The process definition reference. This is not persisted, it is only available when the variable is fetched by a query of process instances + * or tasks that includes variables, or in the transaction that creates or updates the variable. + */ + String getProcessDefinitionId(); + /** * The process instance reference. */ @@ -64,5 +70,11 @@ public interface HistoricVariableInstance extends HistoricData { String getScopeType(); + /** + * The scope definition reference. This is not persisted, it is only available when the variable is fetched by a query of case instances, + * plan item instances or tasks that includes variables, or in the transaction that creates or updates the variable. + */ + String getScopeDefinitionId(); + String getMetaInfo(); } diff --git a/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/persistence/entity/VariableInstance.java b/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/persistence/entity/VariableInstance.java index 5eaa3de538e..f6837954675 100644 --- a/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/persistence/entity/VariableInstance.java +++ b/modules/flowable-variable-service-api/src/main/java/org/flowable/variable/api/persistence/entity/VariableInstance.java @@ -32,6 +32,11 @@ public interface VariableInstance extends ValueFields { void setProcessInstanceId(String processInstanceId); void setProcessDefinitionId(String processDefinitionId); + + /** + * The process definition reference. This is not persisted, it is only available when the variable is fetched by a query of process instances + * or tasks that includes variables, or in the transaction that creates, updates or deletes the variable. + */ String getProcessDefinitionId(); Object getValue(); @@ -56,6 +61,10 @@ default boolean isReadOnly() { void setScopeDefinitionId(String scopeDefinitionId); + /** + * The scope definition reference. This is not persisted, it is only available when the variable is fetched by a query of case instances, + * plan item instances or tasks that includes variables, or in the transaction that creates, updates or deletes the variable. + */ String getScopeDefinitionId(); String getMetaInfo(); diff --git a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntity.java b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntity.java index cc8b670afd5..f400c760081 100644 --- a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntity.java +++ b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntity.java @@ -36,6 +36,8 @@ public interface HistoricVariableInstanceEntity extends ValueFields, HistoricVar void setProcessInstanceId(String processInstanceId); + void setProcessDefinitionId(String processDefinitionId); + void setTaskId(String taskId); void setCreateTime(Date createTime); @@ -50,6 +52,8 @@ public interface HistoricVariableInstanceEntity extends ValueFields, HistoricVar void setScopeType(String scopeType); + void setScopeDefinitionId(String scopeDefinitionId); + ByteArrayRef getByteArrayRef(); void setMetaInfo(String metaInfo); diff --git a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityImpl.java b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityImpl.java index d6143bc1e20..f113f8b090d 100644 --- a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityImpl.java +++ b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityImpl.java @@ -40,6 +40,10 @@ public class HistoricVariableInstanceEntityImpl extends AbstractVariableServiceE protected String subScopeId; protected String scopeType; + // Not persisted, only set when the variable is fetched by a query that includes variables or copied from the runtime variable + protected String processDefinitionId; + protected String scopeDefinitionId; + protected Date createTime; protected Date lastUpdatedTime; @@ -204,6 +208,16 @@ public String getProcessInstanceId() { return processInstanceId; } + @Override + public String getProcessDefinitionId() { + return processDefinitionId; + } + + @Override + public void setProcessDefinitionId(String processDefinitionId) { + this.processDefinitionId = processDefinitionId; + } + @Override public String getTaskId() { return taskId; @@ -279,6 +293,16 @@ public void setScopeType(String scopeType) { this.scopeType = scopeType; } + @Override + public String getScopeDefinitionId() { + return scopeDefinitionId; + } + + @Override + public void setScopeDefinitionId(String scopeDefinitionId) { + this.scopeDefinitionId = scopeDefinitionId; + } + @Override public String getMetaInfo() { return metaInfo; diff --git a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityManagerImpl.java b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityManagerImpl.java index 26685ed61bc..f1d5e0e7f8f 100644 --- a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityManagerImpl.java +++ b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/persistence/entity/HistoricVariableInstanceEntityManagerImpl.java @@ -62,6 +62,7 @@ public HistoricVariableInstanceEntity createAndInsert(VariableInstanceEntity var @Override public void copyVariableFields(HistoricVariableInstanceEntity historicVariableInstance, VariableInstanceEntity variableInstance, Date updateTime) { historicVariableInstance.setProcessInstanceId(variableInstance.getProcessInstanceId()); + historicVariableInstance.setProcessDefinitionId(variableInstance.getProcessDefinitionId()); historicVariableInstance.setExecutionId(variableInstance.getExecutionId()); historicVariableInstance.setTaskId(variableInstance.getTaskId()); historicVariableInstance.setName(variableInstance.getName()); @@ -69,6 +70,7 @@ public void copyVariableFields(HistoricVariableInstanceEntity historicVariableIn historicVariableInstance.setScopeId(variableInstance.getScopeId()); historicVariableInstance.setSubScopeId(variableInstance.getSubScopeId()); historicVariableInstance.setScopeType(variableInstance.getScopeType()); + historicVariableInstance.setScopeDefinitionId(variableInstance.getScopeDefinitionId()); historicVariableInstance.setMetaInfo(variableInstance.getMetaInfo()); copyVariableValue(historicVariableInstance, variableInstance, updateTime);