From 8dc1e580204640cf3d4a539adc47759eee32d472 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 7 Nov 2025 21:24:06 +0100 Subject: [PATCH 1/2] HeadersPayloadExtractor should support case insensitive event headers --- .../BpmnHeaderEventRegistryConsumerTest.java | 40 ++++++++++++++ .../impl/payload/HeadersPayloadExtractor.java | 55 ++++++++----------- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java index 2557c4de174..7970cb989df 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.util.LinkedCaseInsensitiveMap; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -112,6 +113,32 @@ public void testProcessStartWithHeaders() { ); } + @Test + @Deployment(resources = "org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.testProcessStartWithHeaders.bpmn20.xml") + public void testProcessStartWithCaseInsensitiveEventHeaders() { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("process").singleResult(); + assertThat(processDefinition).isNotNull(); + + EventSubscription eventSubscription = runtimeService.createEventSubscriptionQuery() + .processDefinitionId(processDefinition.getId()) + .scopeType(ScopeTypes.BPMN) + .singleResult(); + assertThat(eventSubscription).isNotNull(); + assertThat(eventSubscription.getEventType()).isEqualTo("myEvent"); + + assertThat(runtimeService.createProcessInstanceQuery().list()).isEmpty(); + + inboundEventChannelAdapter.triggerTestEventWithCaseInsensitiveHeaders("payloadStartCustomer", "testHeader", 1234); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey("process").singleResult(); + assertThat(runtimeService.getVariables(processInstance.getId())) + .containsOnly( + entry("customerIdVar", "payloadStartCustomer"), + entry("payload1", "Hello World"), + entry("myHeaderValue1", "testHeader"), + entry("myHeaderValue2", 1234) + ); + } + private static class TestInboundEventChannelAdapter implements InboundEventChannelAdapter { public InboundChannelModel inboundChannelModel; @@ -145,6 +172,19 @@ public void triggerTestEventWithHeaders(String customerId, String headerValue1, } } + public void triggerTestEventWithCaseInsensitiveHeaders(String customerId, String headerValue1, Integer headerValue2) { + ObjectNode eventNode = createTestEventNode(customerId, null); + Map headers = new LinkedCaseInsensitiveMap<>(); + headers.put("HeaderProperty1", headerValue1); + headers.put("HeaderProperty2", headerValue2); + try { + String event = objectMapper.writeValueAsString(eventNode); + eventRegistry.eventReceived(inboundChannelModel, new DefaultInboundEvent(event, headers)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + protected ObjectNode createTestEventNode(String customerId, String orderId) { ObjectNode json = objectMapper.createObjectNode(); json.put("type", "myEvent"); diff --git a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/payload/HeadersPayloadExtractor.java b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/payload/HeadersPayloadExtractor.java index da0b4105a94..b6c8159b016 100644 --- a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/payload/HeadersPayloadExtractor.java +++ b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/payload/HeadersPayloadExtractor.java @@ -13,11 +13,10 @@ package org.flowable.eventregistry.impl.payload; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; import org.flowable.eventregistry.api.FlowableEventInfo; import org.flowable.eventregistry.api.InboundEventInfoAwarePayloadExtractor; @@ -35,52 +34,42 @@ public Collection extractPayload(EventModel eventModel, Fl if (headers.isEmpty()) { return Collections.emptyList(); } - Map filteredHeaders = convertHeaderValues(event, eventModel); - return headers.stream() - .filter(headerDefinition -> filteredHeaders.containsKey(headerDefinition.getName())) - .map(headerDefinition -> new EventPayloadInstanceImpl(headerDefinition, filteredHeaders.get(headerDefinition.getName()))) - .collect(Collectors.toList()); - } - - protected Map convertHeaderValues(FlowableEventInfo eventInfo, EventModel eventModel) { - Map filteredHeaders = new HashMap<>(); - if (eventInfo.getHeaders() != null) { - Map headers = eventInfo.getHeaders(); - for (String headerName : headers.keySet()) { - EventPayload eventHeaderDef = eventModel.getPayload(headerName); - if (eventHeaderDef != null && eventHeaderDef.isHeader()) { - Object headerValueObject = headers.get(headerName); - if (headerValueObject instanceof byte[]) { - byte[] headerValue = (byte[]) headers.get(headerName); - convertBytesHeaderValue(headerName, headerValue, filteredHeaders, eventHeaderDef); - } else { - filteredHeaders.put(headerName, headerValueObject); - } + Map eventHeaders = event.getHeaders(); + if (eventHeaders == null || eventHeaders.isEmpty()) { + return Collections.emptyList(); + } + Collection eventPayloadHeaderInstances = new ArrayList<>(headers.size()); + for (EventPayload header : headers) { + if (eventHeaders.containsKey(header.getName())) { + Object headerValueObject = eventHeaders.get(header.getName()); + Object headerValue = headerValueObject; + if (headerValueObject instanceof byte[] headerValueBytes) { + headerValue = convertBytesHeaderValue(headerValueBytes, header); } + eventPayloadHeaderInstances.add(new EventPayloadInstanceImpl(header, headerValue)); } } - - return filteredHeaders; + return eventPayloadHeaderInstances; } - - protected void convertBytesHeaderValue(String headerName, byte[] headerValue, Map filteredHeaders, EventPayload eventHeaderDef) { + + protected Object convertBytesHeaderValue(byte[] headerValue, EventPayload eventHeaderDef) { if (EventPayloadTypes.STRING.equals(eventHeaderDef.getType())) { - filteredHeaders.put(headerName, convertBytesToString(headerValue)); + return convertBytesToString(headerValue); } else if (EventPayloadTypes.DOUBLE.equals(eventHeaderDef.getType())) { - filteredHeaders.put(headerName, Double.valueOf(convertBytesToString(headerValue))); + return Double.valueOf(convertBytesToString(headerValue)); } else if (EventPayloadTypes.INTEGER.equals(eventHeaderDef.getType())) { - filteredHeaders.put(headerName, Integer.valueOf(convertBytesToString(headerValue))); + return Integer.valueOf(convertBytesToString(headerValue)); } else if (EventPayloadTypes.LONG.equals(eventHeaderDef.getType())) { - filteredHeaders.put(headerName, Long.valueOf(convertBytesToString(headerValue))); + return Long.valueOf(convertBytesToString(headerValue)); } else if (EventPayloadTypes.BOOLEAN.equals(eventHeaderDef.getType())) { - filteredHeaders.put(headerName, Boolean.valueOf(convertBytesToString(headerValue))); + return Boolean.valueOf(convertBytesToString(headerValue)); } else { - filteredHeaders.put(headerName, headerValue); + return headerValue; } } From 58e16d382012714967a0418abfe9fcc824d706c6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 10 Nov 2025 09:22:43 +0100 Subject: [PATCH 2/2] Add additional test with missing headers --- .../BpmnHeaderEventRegistryConsumerTest.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java index 7970cb989df..fe4ce7280d4 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.java @@ -113,6 +113,32 @@ public void testProcessStartWithHeaders() { ); } + @Test + @Deployment(resources = "org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.testProcessStartWithHeaders.bpmn20.xml") + public void testProcessStartWithMissingHeaders() { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("process").singleResult(); + assertThat(processDefinition).isNotNull(); + + EventSubscription eventSubscription = runtimeService.createEventSubscriptionQuery() + .processDefinitionId(processDefinition.getId()) + .scopeType(ScopeTypes.BPMN) + .singleResult(); + assertThat(eventSubscription).isNotNull(); + assertThat(eventSubscription.getEventType()).isEqualTo("myEvent"); + + assertThat(runtimeService.createProcessInstanceQuery().list()).isEmpty(); + + inboundEventChannelAdapter.triggerTestEventWithHeaders("payloadStartCustomer", null, null); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey("process").singleResult(); + assertThat(runtimeService.getVariables(processInstance.getId())) + .containsOnly( + entry("customerIdVar", "payloadStartCustomer"), + entry("payload1", "Hello World"), + entry("myHeaderValue1", null), + entry("myHeaderValue2", null) + ); + } + @Test @Deployment(resources = "org/flowable/engine/test/eventregistry/BpmnHeaderEventRegistryConsumerTest.testProcessStartWithHeaders.bpmn20.xml") public void testProcessStartWithCaseInsensitiveEventHeaders() { @@ -162,8 +188,12 @@ public void setEventRegistry(EventRegistry eventRegistry) { public void triggerTestEventWithHeaders(String customerId, String headerValue1, Integer headerValue2) { ObjectNode eventNode = createTestEventNode(customerId, null); Map headers = new HashMap<>(); - headers.put("headerProperty1", headerValue1); - headers.put("headerProperty2", headerValue2); + if (headerValue1 != null) { + headers.put("headerProperty1", headerValue1); + } + if (headerValue2 != null) { + headers.put("headerProperty2", headerValue2); + } try { String event = objectMapper.writeValueAsString(eventNode); eventRegistry.eventReceived(inboundChannelModel, new DefaultInboundEvent(event, headers));