This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/29158-to-camel-4.22.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5d691504603231d85f13339f95fcb79c7b4f6034 Author: Guillaume Nodet <[email protected]> AuthorDate: Wed Nov 4 15:36:03 2026 +0000 [backport camel-4.22.x] CAMEL-26585: camel-aws2-sns - serialize array attributes as valid JSON --- .../camel/component/aws2/sns/Sns2Producer.java | 13 +++++- .../camel/component/aws2/sns/SnsProducerTest.java | 50 ++++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 11 +++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java index ed7a28661363..cdc8035fe2a4 100644 --- a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java +++ b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java @@ -35,6 +35,7 @@ import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.support.DefaultProducer; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.URISupport; +import org.apache.camel.util.json.Jsoner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.core.SdkBytes; @@ -153,7 +154,7 @@ public class Sns2Producer extends DefaultProducer { result.put(entry.getKey(), mav.build()); } else if (value instanceof List) { String resultString = ((List<?>) value).stream() - .map(o -> o instanceof String ? String.format("\"%s\"", o) : Objects.toString(o)) + .map(Sns2Producer::toStringArrayValue) .collect(Collectors.joining(", ")); MessageAttributeValue.Builder mav = MessageAttributeValue.builder(); mav.dataType("String.Array"); @@ -170,6 +171,16 @@ public class Sns2Producer extends DefaultProducer { return result; } + private static String toStringArrayValue(Object value) { + boolean jsonPrimitive = value == null || value instanceof Boolean || value instanceof Number; + if (value instanceof Double number) { + jsonPrimitive = Double.isFinite(number); + } else if (value instanceof Float number) { + jsonPrimitive = Float.isFinite(number); + } + return jsonPrimitive ? Objects.toString(value) : "\"" + Jsoner.escape(value.toString()) + "\""; + } + private void configureFifoAttributes(PublishRequest.Builder request, Exchange exchange) { if (getEndpoint().getConfiguration().isFifoTopic()) { // use strategies diff --git a/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java b/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java index a780c490cb2d..f9154b68c223 100644 --- a/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java +++ b/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java @@ -17,10 +17,14 @@ package org.apache.camel.component.aws2.sns; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.camel.Exchange; +import org.apache.camel.util.json.Jsoner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -48,6 +52,52 @@ public class SnsProducerTest { when(endpoint.getHeaderFilterStrategy()).thenReturn(new Sns2HeaderFilterStrategy()); } + @Test + public void translateStringArrayEscapesJsonCharacters() throws Exception { + List<String> values = List.of("a\"b", "C:\\temp", "line\nbreak", "tab\tvalue", "\b\f\r\u0001", "caf\u00e9", + "application/json", "\u007f", "\u2000"); + + MessageAttributeValue attribute = producer.translateAttributes(Map.of("values", values), exchange).get("values"); + + assertThat(attribute.dataType(), is("String.Array")); + assertThat(Jsoner.deserialize(attribute.stringValue()), is(values)); + } + + @Test + public void translateStringArrayQuotesOtherMemberTypes() throws Exception { + UUID id = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + Date date = new Date(0); + List<Object> values = List.of(Membership.GOLD, 'x', id, date); + + MessageAttributeValue attribute = producer.translateAttributes(Map.of("values", values), exchange).get("values"); + + assertThat(Jsoner.deserialize(attribute.stringValue()), is(List.of("GOLD", "x", id.toString(), date.toString()))); + } + + @Test + public void translateStringArrayQuotesNonFiniteNumbers() throws Exception { + List<Number> values = List.of(Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, + Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY); + + MessageAttributeValue attribute = producer.translateAttributes(Map.of("values", values), exchange).get("values"); + + assertThat(Jsoner.deserialize(attribute.stringValue()), + is(List.of("NaN", "Infinity", "-Infinity", "NaN", "Infinity", "-Infinity"))); + } + + @Test + public void translateStringArrayKeepsJsonPrimitives() { + List<Object> values = Arrays.asList(12, 1.5D, 2.5F, true, null); + + MessageAttributeValue attribute = producer.translateAttributes(Map.of("values", values), exchange).get("values"); + + assertThat(attribute.stringValue(), is("[12, 1.5, 2.5, true, null]")); + } + + private enum Membership { + GOLD + } + @Test public void translateAttributes() { Map<String, Object> headers = new HashMap<>(); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index 4ffee56c75ca..3afd44e3029f 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -1600,6 +1600,17 @@ or when the queue is FIFO (AWS rejects per-message delay with `InvalidParameterV and was redundant on delay queues. If your route relied on per-message delay overrides on a delay queue, the override will now be silently ignored. +=== camel-aws2-sns - string array message attributes + +String values in list-valued message headers are now JSON-escaped when mapped to SNS +`String.Array` message attributes. Quotes, backslashes and control characters are preserved +as string data. Routes should supply the original strings rather than pre-escaping them. +Other member types, such as enums, characters, UUIDs and dates, are serialized as JSON strings. +Non-finite floating-point values (`NaN` and infinities) are also serialized as strings; finite numbers, +booleans and null remain JSON primitives. +The JSON utility also escapes forward slashes and some Unicode characters, so raw `String.Array` +attribute text may differ even when its decoded string values are unchanged. + === camel-xslt-saxon - secure processing now applied unconditionally The `secureProcessing` option (default `true`) is now applied to the Saxon `TransformerFactory`
