From 630a6c7761d865849351480225d9f786957278f4 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 23 Jan 2024 10:41:00 +0100 Subject: [PATCH] Fix incorrect computation of all available correlation keys Use a new approach based on https://simonhessner.de/calculate-power-set-set-of-all-subsets-in-python-without-recursion/ to generate the power set for the correlation keys --- ...AbstractBpmnEventRegistryConsumerTest.java | 6 +- .../BpmnEventRegistryConsumerTest.java | 188 ++++++ ...ith3CorrelationsAllCombinations.bpmn20.xml | 336 ++++++++++ ...ith4CorrelationsAllCombinations.bpmn20.xml | 611 ++++++++++++++++++ .../BaseEventRegistryEventConsumer.java | 48 +- 5 files changed, 1183 insertions(+), 6 deletions(-) create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith3CorrelationsAllCombinations.bpmn20.xml create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith4CorrelationsAllCombinations.bpmn20.xml diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/AbstractBpmnEventRegistryConsumerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/AbstractBpmnEventRegistryConsumerTest.java index e905f0f3d96..46bec2b548a 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/AbstractBpmnEventRegistryConsumerTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/AbstractBpmnEventRegistryConsumerTest.java @@ -95,7 +95,7 @@ public void setEventRegistry(EventRegistry eventRegistry) { } public void triggerTestEvent() { - triggerTestEvent(null); + triggerTestEvent((String) null); } public void triggerTestEvent(String customerId) { @@ -108,6 +108,10 @@ public void triggerOrderTestEvent(String orderId) { public void triggerTestEvent(String customerId, String orderId) { ObjectNode eventNode = createTestEventNode(customerId, orderId); + triggerTestEvent(eventNode); + } + + public void triggerTestEvent(ObjectNode eventNode) { try { eventRegistry.eventReceived(inboundChannelModel, objectMapper.writeValueAsString(eventNode)); } catch (JsonProcessingException e) { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.java index af07d4843f3..0fcd6e1ce83 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.java @@ -27,10 +27,13 @@ import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.Deployment; +import org.flowable.eventregistry.api.model.EventPayloadTypes; import org.flowable.eventsubscription.api.EventSubscription; import org.flowable.task.api.Task; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.node.ObjectNode; + public class BpmnEventRegistryConsumerTest extends AbstractBpmnEventRegistryConsumerTest { @Test @@ -122,6 +125,191 @@ public void testBoundaryEventListenerWithPayload() { assertThat(eventSubscription).isNull(); } + @Test + @Deployment + public void testBoundaryEventWith3CorrelationsAllCombinations() { + // The BPMN has boundary events for each combination, the correlation matches a new task will be created + getEventRepositoryService().createEventModelBuilder() + .key("customer") + .resourceName("customer.event") + .correlationParameter("id", EventPayloadTypes.STRING) + .correlationParameter("first", EventPayloadTypes.STRING) + .correlationParameter("last", EventPayloadTypes.STRING) + .deploy(); + + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("multiCorrelationProcess") + .variable("customerId", "1234") + .variable("customerFirstName", "John") + .variable("customerLastName", "Doe") + .start(); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder("User task"); + + ObjectNode event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "customer") + .put("id", "1235") + .put("first", "Jane") + .put("last", "Doele"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder("User task"); + + event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "customer") + .put("id", "1234") + .put("first", "John") + .put("last", "Doe"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder( + "User task", + "Customer ID Task", + "Customer ID and First Name Task", + "Customer ID and Last Name Task", + "Customer ID, First Name and Last Name Task", + "First Name Task", + "First Name and Last Name Task", + "Last Name Task" + ); + } + + @Test + @Deployment + public void testBoundaryEventWith4CorrelationsAllCombinations() { + // The BPMN has boundary events for each combination, the correlation matches a new task will be created + getEventRepositoryService().createEventModelBuilder() + .key("testEvent") + .resourceName("test.event") + .correlationParameter("id", EventPayloadTypes.STRING) + .correlationParameter("orderId", EventPayloadTypes.STRING) + .correlationParameter("firstName", EventPayloadTypes.STRING) + .correlationParameter("lastName", EventPayloadTypes.STRING) + .deploy(); + + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("multiCorrelationProcess") + .variable("customerId", "customer-1") + .variable("orderId", "order-1") + .variable("firstName", "John") + .variable("lastName", "Doe") + .start(); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder("User task"); + + ObjectNode event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "testEvent") + .put("id", "customer-2") + .put("orderId", "order-2") + .put("firstName", "Jane") + .put("lastName", "Doele"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder("User task"); + + event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "testEvent") + .put("id", "customer-1") + .put("orderId", "order-1") + .put("firstName", "John") + .put("lastName", "Doe"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder( + "User task", + "ID", + "Order ID", + "First Name", + "Last Name", + "ID and Order ID", + "ID and First Name", + "ID and Last Name", + "Order ID and First Name", + "Order ID and Last Name", + "First Name and Last Name", + "ID, Order ID and First Name", + "ID, Order ID and Last Name", + "ID, First Name and Last Name", + "Order ID, First Name and Last Name", + "ID, Order ID, First Name and Last Name" + ); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith4CorrelationsAllCombinations.bpmn20.xml") + public void testBoundaryEventWith4CorrelationsSubsetOfCombinations() { + // The BPMN has boundary events for each combination, the correlation matches a new task will be created + // In this test we are going to match a subset of the tasks + getEventRepositoryService().createEventModelBuilder() + .key("testEvent") + .resourceName("test.event") + .correlationParameter("id", EventPayloadTypes.STRING) + .correlationParameter("orderId", EventPayloadTypes.STRING) + .correlationParameter("firstName", EventPayloadTypes.STRING) + .correlationParameter("lastName", EventPayloadTypes.STRING) + .deploy(); + + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("multiCorrelationProcess") + .variable("customerId", "customer-1") + .variable("orderId", "order-1") + .variable("firstName", "John") + .variable("lastName", "Doe") + .start(); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder("User task"); + + ObjectNode event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "testEvent") + .put("id", "customer-2") + .put("orderId", "order-1") + .put("firstName", "Jane") + .put("lastName", "Doe"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder( + "User task", + "Order ID", + "Last Name", + "Order ID and Last Name" + ); + + event = processEngineConfiguration.getObjectMapper().createObjectNode() + .put("type", "testEvent") + .put("id", "customer-2") + .put("orderId", "order-2") + .put("firstName", "John") + .put("lastName", "Smith"); + inboundEventChannelAdapter.triggerTestEvent(event); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getName) + .containsExactlyInAnyOrder( + "User task", + "Order ID", + "Last Name", + "Order ID and Last Name", + // The first name is from the John Smith trigger + "First Name" + ); + } + @Test @Deployment public void testReceiveEventTaskNoCorrelation() { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith3CorrelationsAllCombinations.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith3CorrelationsAllCombinations.bpmn20.xml new file mode 100644 index 00000000000..7aaa32c94ac --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith3CorrelationsAllCombinations.bpmn20.xml @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith4CorrelationsAllCombinations.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith4CorrelationsAllCombinations.bpmn20.xml new file mode 100644 index 00000000000..15137a8d950 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/eventregistry/BpmnEventRegistryConsumerTest.testBoundaryEventWith4CorrelationsAllCombinations.bpmn20.xml @@ -0,0 +1,611 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/consumer/BaseEventRegistryEventConsumer.java b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/consumer/BaseEventRegistryEventConsumer.java index fe9b2177bc2..dc520ced1b9 100644 --- a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/consumer/BaseEventRegistryEventConsumer.java +++ b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/consumer/BaseEventRegistryEventConsumer.java @@ -77,14 +77,52 @@ protected Collection generateCorrelationKeys(Collection correlationKeys = new HashSet<>(); + + String allParametersCorrelationKey = generateCorrelationKey(correlationParameterInstances); + correlationKeys.add(new CorrelationKey(allParametersCorrelationKey, correlationParameterInstances)); + + for (EventPayloadInstance correlationParameterInstance : correlationParameterInstances) { + Set singleParameterInstance = Collections.singleton(correlationParameterInstance); + String correlationKey = generateCorrelationKey(singleParameterInstance); + correlationKeys.add(new CorrelationKey(correlationKey, singleParameterInstance)); + } + return correlationKeys; + } + + // The correlation keys are the power set of the correlation parameter instances minus the empty set. + // A power set is a set of all subsets including the empty set and the set itself. + // A power set has a size of 2^n where n is the size of the set. + // e.g. power set for [A, B] is [ [], [A], [B], [A, B] ] + // We are going to compute the correlation keys by iterating from 1 to 2^n and the binary representation of every iteration index + // will be used to determine if the index should be included in the set or not + // We will start from 1 because we want to skip the empty set + // This is an adaptation of https://simonhessner.de/calculate-power-set-set-of-all-subsets-in-python-without-recursion/ List list = new ArrayList<>(correlationParameterInstances); + + int correlationKeysSize = Math.toIntExact((long) Math.pow(2, list.size())); Collection correlationKeys = new HashSet<>(); - for (int i = 1; i <= list.size(); i++) { - for (int j = 0; j <= list.size() - i; j++) { - List parameterSubList = list.subList(j, j + i); - String correlationKey = generateCorrelationKey(parameterSubList); - correlationKeys.add(new CorrelationKey(correlationKey, parameterSubList)); + + for (int counter = 1; counter < correlationKeysSize; counter++) { + Collection subset = new ArrayList<>(list.size()); + for (int i = 0; i < list.size(); i++) { + if ((counter & (1 << i)) > 0) { + // Here we check if the index should be included in the combination + subset.add(list.get(i)); + } } + + String correlationKey = generateCorrelationKey(subset); + correlationKeys.add(new CorrelationKey(correlationKey, subset)); } return correlationKeys;