From f418d157a07a20c1524173c5207f1f28c77a8932 Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Thu, 8 Feb 2024 21:51:02 +0100 Subject: [PATCH] Support listening to a variable change event with multiple elements in bpmn and cmmn --- ...aluateVariableEventListenersOperation.java | 10 +-- .../VariableEventListenerTest.java | 68 +++++++++++++++++++ ...TriggerMultipleVariableEventListeners.cmmn | 33 +++++++++ .../VariableListenerSessionData.java | 12 +++- ...ableListenerEventDefinitionsOperation.java | 6 +- .../variable/VariableListenerEventTest.java | 22 ++++++ ....multipleCatchVariableListeners.bpmn20.xml | 51 ++++++++++++++ 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java index c5ef2fec146..737b716b092 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java @@ -93,10 +93,12 @@ public void run() { if (changeTypeValue.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); - triggeredPlanItemInstance = true; + + if (!variableListenerData.containsProcessedElementId(planItemInstance.getPlanItemDefinitionId())) { + CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); + triggeredPlanItemInstance = true; + variableListenerData.addProcessedElementId(planItemInstance.getPlanItemDefinitionId()); + } } } } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java index 1d08e3b7f26..ce4761f5605 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java @@ -196,6 +196,74 @@ public void testTriggerVariableEventListenerInStageOnlyCreate() { assertCaseInstanceEnded(caseInstance); } + + @Test + @CmmnDeployment + public void testTriggerMultipleVariableEventListeners() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("variableListener").start(); + + // 5 plan items reachable + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().count()).isEqualTo(5); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + } + + // create different variable + cmmnRuntimeService.setVariable(caseInstance.getId(), "var2", "test"); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isEqualTo(2); + + // create var1 variable to trigger variable event listener + cmmnRuntimeService.setVariable(caseInstance.getId(), "var1", "test"); + + // variable event listener should be completed + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isZero(); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskB").planItemInstanceStateActive().count()).isEqualTo(1); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskC").planItemInstanceStateActive().count()).isEqualTo(1); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + } + + + assertCaseInstanceNotEnded(caseInstance); + cmmnTaskService.createTaskQuery().list().forEach(t -> cmmnTaskService.complete(t.getId())); + assertCaseInstanceEnded(caseInstance); + } @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn new file mode 100644 index 00000000000..8b1390ca4cc --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + occur + + + + + occur + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java index d21d3d5e916..99049a3af51 100644 --- a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java @@ -12,6 +12,9 @@ */ package org.flowable.common.engine.impl.variablelistener; +import java.util.ArrayList; +import java.util.List; + public class VariableListenerSessionData { public static final String VARIABLE_CREATE = "create"; @@ -22,6 +25,7 @@ public class VariableListenerSessionData { protected String scopeId; protected String scopeType; protected String scopeDefinitionId; + protected List processedElementIds = new ArrayList<>(); public VariableListenerSessionData(String changeType, String scopeId, String scopeType, String scopeDefinitionId) { this.changeType = changeType; @@ -57,5 +61,11 @@ public String getScopeDefinitionId() { } public void setScopeDefinitionId(String scopeDefinitionId) { this.scopeDefinitionId = scopeDefinitionId; - } + } + public boolean containsProcessedElementId(String elementId) { + return this.processedElementIds.contains(elementId); + } + public void addProcessedElementId(String elementId) { + this.processedElementIds.add(elementId); + } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java index 8691987d497..ac6e2bce53a 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java @@ -106,8 +106,10 @@ public void run() { VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + if (!variableListenerData.containsProcessedElementId(execution.getActivityId())) { + CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + variableListenerData.addProcessedElementId(execution.getActivityId()); + } } } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java index 6291fc4fb72..15661b7efc0 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java @@ -88,6 +88,28 @@ public void catchVariableListenerUpdate() { assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(0); } + @Test + @Deployment + public void multipleCatchVariableListeners() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("catchVariableListener"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(3); + + runtimeService.setVariable(processInstance.getId(), "var2", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + runtimeService.setVariable(processInstance.getId(), "var1", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(0); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(2); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask2").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + } + @Test @Deployment public void boundaryVariableListener() { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml new file mode 100644 index 00000000000..f3595d15fc9 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +