diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonIndexAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonIndexAccessor.java
new file mode 100644
index 0000000000..3412486293
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonIndexAccessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.json;
+
+import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.node.ArrayNode;
+
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.IndexAccessor;
+import org.springframework.expression.TypedValue;
+
+/**
+ * A SpEL {@link IndexAccessor} that knows how to read indexes from JSON arrays, using
+ * Jackson's {@link ArrayNode} API.
+ *
+ *
Supports indexes supplied as an integer literal — for example, {@code myJsonArray[1]}.
+ * Also supports negative indexes — for example, {@code myJsonArray[-1]} which equates
+ * to {@code myJsonArray[myJsonArray.length - 1]}. Furthermore, {@code null} is returned for
+ * any index that is out of bounds (see {@link ArrayNode#get(int)} for details).
+ *
+ * @author Jooyoung Pyoung
+ *
+ * @since 7.0
+ * @see JacksonPropertyAccessor
+ */
+public class JacksonIndexAccessor implements IndexAccessor {
+
+ private static final Class>[] SUPPORTED_CLASSES = { ArrayNode.class };
+
+ @Override
+ public Class>[] getSpecificTargetClasses() {
+ return SUPPORTED_CLASSES;
+ }
+
+ @Override
+ public boolean canRead(EvaluationContext context, Object target, Object index) {
+ return (target instanceof ArrayNode && index instanceof Integer);
+ }
+
+ @Override
+ public TypedValue read(EvaluationContext context, Object target, Object index) throws AccessException {
+ ArrayNode arrayNode = (ArrayNode) target;
+ Integer intIndex = (Integer) index;
+ if (intIndex < 0) {
+ // negative index: get from the end of array, for compatibility with JacksonPropertyAccessor.ArrayNodeAsList.
+ intIndex = arrayNode.size() + intIndex;
+ }
+ return JacksonPropertyAccessor.typedValue(arrayNode.get(intIndex));
+ }
+
+ @Override
+ public boolean canWrite(EvaluationContext context, Object target, Object index) {
+ return false;
+ }
+
+ @Override
+ public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) {
+ throw new UnsupportedOperationException("Write is not supported");
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonPropertyAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonPropertyAccessor.java
new file mode 100644
index 0000000000..17be286db6
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonPropertyAccessor.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.json;
+
+import java.util.AbstractList;
+import java.util.Iterator;
+
+import org.jspecify.annotations.Nullable;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.databind.node.ArrayNode;
+import tools.jackson.databind.node.NullNode;
+
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.PropertyAccessor;
+import org.springframework.expression.TypedValue;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A SpEL {@link PropertyAccessor} that knows how to read properties from JSON objects.
+ *
Uses Jackson {@link JsonNode} API for nested properties access.
+ *
+ * @author Jooyoung Pyoung
+ *
+ * @since 7.0
+ * @see JacksonIndexAccessor
+ */
+public class JacksonPropertyAccessor implements PropertyAccessor {
+
+ /**
+ * The kind of types this can work with.
+ */
+ private static final Class>[] SUPPORTED_CLASSES =
+ {
+ String.class,
+ JsonNodeWrapper.class,
+ JsonNode.class
+ };
+
+ private ObjectMapper objectMapper = JsonMapper.builder()
+ .findAndAddModules(JacksonPropertyAccessor.class.getClassLoader())
+ .build();
+
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "'objectMapper' cannot be null");
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public Class>[] getSpecificTargetClasses() {
+ return SUPPORTED_CLASSES;
+ }
+
+ @Override
+ public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+ JsonNode node;
+ try {
+ node = asJson(target);
+ }
+ catch (AccessException e) {
+ // Cannot parse - treat as not a JSON
+ return false;
+ }
+ if (node instanceof ArrayNode) {
+ return maybeIndex(name) != null;
+ }
+ return true;
+ }
+
+ private JsonNode asJson(Object target) throws AccessException {
+ if (target instanceof JsonNode jsonNode) {
+ return jsonNode;
+ }
+ else if (target instanceof JsonNodeWrapper> jsonNodeWrapper) {
+ return jsonNodeWrapper.getRealNode();
+ }
+ else if (target instanceof String content) {
+ try {
+ return this.objectMapper.readTree(content);
+ }
+ catch (JacksonException e) {
+ throw new AccessException("Exception while trying to deserialize String", e);
+ }
+ }
+ else {
+ throw new IllegalStateException("Can't happen. Check SUPPORTED_CLASSES");
+ }
+ }
+
+ /**
+ * Return an integer if the String property name can be parsed as an int, or null otherwise.
+ */
+ private static Integer maybeIndex(String name) {
+ if (!isNumeric(name)) {
+ return null;
+ }
+ try {
+ return Integer.valueOf(name);
+ }
+ catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
+ JsonNode node = asJson(target);
+ Integer index = maybeIndex(name);
+ if (index != null && node.has(index)) {
+ return typedValue(node.get(index));
+ }
+ else {
+ return typedValue(node.get(name));
+ }
+ }
+
+ @Override
+ public boolean canWrite(EvaluationContext context, Object target, String name) {
+ return false;
+ }
+
+ @Override
+ public void write(EvaluationContext context, Object target, String name, Object newValue) {
+ throw new UnsupportedOperationException("Write is not supported");
+ }
+
+ /**
+ * Check if the string is a numeric representation (all digits) or not.
+ */
+ private static boolean isNumeric(String str) {
+ if (!StringUtils.hasLength(str)) {
+ return false;
+ }
+ int length = str.length();
+ for (int i = 0; i < length; i++) {
+ if (!Character.isDigit(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static TypedValue typedValue(JsonNode json) throws AccessException {
+ if (json == null || json instanceof NullNode) {
+ return TypedValue.NULL;
+ }
+ else if (json.isValueNode()) {
+ return new TypedValue(getValue(json));
+ }
+ return new TypedValue(wrap(json));
+ }
+
+ private static Object getValue(JsonNode json) throws AccessException {
+ if (json.isString()) {
+ return json.asString();
+ }
+ else if (json.isNumber()) {
+ return json.numberValue();
+ }
+ else if (json.isBoolean()) {
+ return json.asBoolean();
+ }
+ else if (json.isNull()) {
+ return null;
+ }
+ else if (json.isBinary()) {
+ try {
+ return json.binaryValue();
+ }
+ catch (JacksonException e) {
+ throw new AccessException(
+ "Can not get content of binary value: " + json, e);
+ }
+ }
+ throw new IllegalArgumentException("Json is not ValueNode.");
+ }
+
+ public static Object wrap(JsonNode json) throws AccessException {
+ if (json == null) {
+ return null;
+ }
+ else if (json instanceof ArrayNode arrayNode) {
+ return new ArrayNodeAsList(arrayNode);
+ }
+ else if (json.isValueNode()) {
+ return getValue(json);
+ }
+ else {
+ return new ComparableJsonNode(json);
+ }
+ }
+
+ interface JsonNodeWrapper extends Comparable {
+
+ JsonNode getRealNode();
+
+ }
+
+ static class ComparableJsonNode implements JsonNodeWrapper {
+
+ private final JsonNode delegate;
+
+ ComparableJsonNode(JsonNode delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public JsonNode getRealNode() {
+ return this.delegate;
+ }
+
+ @Override
+ public String toString() {
+ return this.delegate.toString();
+ }
+
+ @Override
+ public int compareTo(ComparableJsonNode o) {
+ return this.delegate.equals(o.delegate) ? 0 : 1;
+ }
+
+ }
+
+ /**
+ * An {@link AbstractList} implementation around {@link ArrayNode} with {@link JsonNodeWrapper} aspect.
+ * @since 5.0
+ */
+ static class ArrayNodeAsList extends AbstractList implements JsonNodeWrapper {
+
+ private final ArrayNode delegate;
+
+ ArrayNodeAsList(ArrayNode node) {
+ this.delegate = node;
+ }
+
+ @Override
+ public JsonNode getRealNode() {
+ return this.delegate;
+ }
+
+ @Override
+ public String toString() {
+ return this.delegate.toString();
+ }
+
+ @Override
+ public Object get(int index) {
+ // negative index - get from the end of list
+ int i = index < 0 ? this.delegate.size() + index : index;
+ try {
+ return wrap(this.delegate.get(i));
+ }
+ catch (AccessException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ @Override
+ public int size() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public Iterator iterator() {
+
+ return new Iterator<>() {
+
+ private final Iterator it = ArrayNodeAsList.this.delegate.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return this.it.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ try {
+ return wrap(this.it.next());
+ }
+ catch (AccessException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ };
+ }
+
+ @Override
+ public int compareTo(Object o) {
+ Object that = (o instanceof JsonNodeWrapper> wrapper ? wrapper.getRealNode() : o);
+ return this.delegate.equals(that) ? 0 : 1;
+ }
+
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonIndexAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonIndexAccessor.java
index 05443044c8..dd69153226 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonIndexAccessor.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonIndexAccessor.java
@@ -34,9 +34,12 @@
* any index that is out of bounds (see {@link ArrayNode#get(int)} for details).
*
* @author Sam Brannen
+ *
* @since 6.4
* @see JsonPropertyAccessor
+ * @deprecated Since 7.0 in favor of {@link JacksonIndexAccessor} for Jackson 3.
*/
+@Deprecated(forRemoval = true, since = "7.0")
public class JsonIndexAccessor implements IndexAccessor {
private static final Class>[] SUPPORTED_CLASSES = { ArrayNode.class };
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperConverter.java
new file mode 100644
index 0000000000..5fc078a554
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperConverter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.json;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.JsonNode;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.GenericConverter;
+import org.springframework.integration.json.JacksonPropertyAccessor.JsonNodeWrapper;
+
+/**
+ * The {@link org.springframework.core.convert.converter.Converter} implementation for the conversion
+ * of {@link JsonNodeWrapper} to {@link JsonNode},
+ * when the {@link JsonNodeWrapper} can be a result of the expression
+ * for JSON in case of the {@link JacksonPropertyAccessor} usage.
+ *
+ * @author Jooyoung Pyoung
+ *
+ * @since 7.0
+ */
+public class JsonNodeWrapperConverter implements GenericConverter {
+
+ @Override
+ public Set getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(JsonNodeWrapper.class, JsonNode.class));
+ }
+
+ @Override
+ @Nullable
+ public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source != null) {
+ return targetType.getObjectType().cast(((JsonNodeWrapper>) source).getRealNode());
+ }
+ return null;
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperToJsonNodeConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperToJsonNodeConverter.java
index cbf6d7788d..6922ec6e45 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperToJsonNodeConverter.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonNodeWrapperToJsonNodeConverter.java
@@ -24,7 +24,6 @@
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
-import org.springframework.integration.json.JsonPropertyAccessor.JsonNodeWrapper;
/**
* The {@link org.springframework.core.convert.converter.Converter} implementation for the conversion
@@ -36,19 +35,22 @@
* @author Artem Bilan
*
* @since 5.5
+ * @deprecated Since 7.0 in favor of {@link JsonNodeWrapperConverter} for Jackson 3.
*/
+@SuppressWarnings("removal")
+@Deprecated(forRemoval = true, since = "7.0")
public class JsonNodeWrapperToJsonNodeConverter implements GenericConverter {
@Override
public Set getConvertibleTypes() {
- return Collections.singleton(new ConvertiblePair(JsonNodeWrapper.class, JsonNode.class));
+ return Collections.singleton(new ConvertiblePair(JsonPropertyAccessor.JsonNodeWrapper.class, JsonNode.class));
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source != null) {
- return targetType.getObjectType().cast(((JsonNodeWrapper>) source).getRealNode());
+ return targetType.getObjectType().cast(((JsonPropertyAccessor.JsonNodeWrapper>) source).getRealNode());
}
return null;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java
index 573420c034..ba9e78f057 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java
@@ -48,7 +48,9 @@
*
* @since 3.0
* @see JsonIndexAccessor
+ * @deprecated Since 7.0 in favor of {@link JacksonPropertyAccessor} for Jackson 3.
*/
+@Deprecated(forRemoval = true, since = "7.0")
public class JsonPropertyAccessor implements PropertyAccessor {
/**
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapper.java
new file mode 100644
index 0000000000..bdf2898125
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapper.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.support.json;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jspecify.annotations.Nullable;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
+
+import org.springframework.integration.mapping.BytesMessageMapper;
+import org.springframework.integration.support.MutableMessage;
+import org.springframework.integration.support.MutableMessageHeaders;
+import org.springframework.integration.support.utils.PatternMatchUtils;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.GenericMessage;
+
+/**
+ * For outbound messages, uses a message-aware Jackson object mapper to render the message
+ * as JSON. For messages with {@code byte[]} payloads, if rendered as JSON, Jackson
+ * performs Base64 conversion on the bytes. If payload is {@code byte[]} and
+ * the {@link #setRawBytes(boolean) rawBytes} property is true (default), the result has the form
+ * {@code [headersLen][headers][payloadLen][payload]}; with the headers
+ * rendered in JSON and the payload unchanged.
+ * Otherwise, message is fully serialized and deserialized with Jackson object mapper.
+ *
+ * By default, all headers are included; you can provide simple patterns to specify a
+ * subset of headers.
+ *
+ * If neither expected format is detected, nor an error occurs during conversion, the
+ * payload of the message is the original {@code byte[]}.
+ *
+ * IMPORTANT
+ *
+ * The default object mapper will only deserialize classes in certain packages.
+ *
+ *
+ * "java.util",
+ * "java.lang",
+ * "org.springframework.messaging.support",
+ * "org.springframework.integration.support",
+ * "org.springframework.integration.message",
+ * "org.springframework.integration.store",
+ * "org.springframework.integration.history",
+ * "org.springframework.integration.handler"
+ *
+ *
+ * To add more packages, create an object mapper using
+ * {@link JacksonMessagingUtils#messagingAwareMapper(String...)}.
+ *
+ * A constructor is provided allowing the provision of such a configured object mapper.
+ *
+ * @author Jooyoung Pyoung
+ *
+ * @since 7.0
+ */
+public class EmbeddedHeadersJsonMessageMapper implements BytesMessageMapper {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final ObjectMapper objectMapper;
+
+ private final String[] headerPatterns;
+
+ private final boolean allHeaders;
+
+ private boolean rawBytes = true;
+
+ private boolean caseSensitive;
+
+ /**
+ * Construct an instance that embeds all headers, using the default
+ * JSON Object mapper.
+ */
+ public EmbeddedHeadersJsonMessageMapper() {
+ this("*");
+ }
+
+ /**
+ * Construct an instance that embeds headers matching the supplied patterns, using
+ * the default JSON object mapper.
+ * @param headerPatterns the patterns.
+ * @see PatternMatchUtils#smartMatch(String, String...)
+ */
+ public EmbeddedHeadersJsonMessageMapper(String... headerPatterns) {
+ this(JacksonMessagingUtils.messagingAwareMapper(), headerPatterns);
+ }
+
+ /**
+ * Construct an instance that embeds all headers, using the
+ * supplied JSON object mapper.
+ * @param objectMapper the object mapper.
+ */
+ public EmbeddedHeadersJsonMessageMapper(ObjectMapper objectMapper) {
+ this(objectMapper, "*");
+ }
+
+ /**
+ * Construct an instance that embeds headers matching the supplied patterns using the
+ * supplied JSON object mapper.
+ * @param objectMapper the object mapper.
+ * @param headerPatterns the patterns.
+ */
+ public EmbeddedHeadersJsonMessageMapper(ObjectMapper objectMapper, String... headerPatterns) {
+ this.objectMapper = objectMapper;
+ this.headerPatterns = Arrays.copyOf(headerPatterns, headerPatterns.length);
+ this.allHeaders = this.headerPatterns.length == 1 && this.headerPatterns[0].equals("*");
+ }
+
+ /**
+ * For messages with {@code byte[]} payloads, if rendered as JSON, Jackson performs
+ * Base64 conversion on the bytes. If this property is true (default), the result has
+ * the form {@code [headersLen][headers][payloadLen][payload]}; with
+ * the headers rendered in JSON and the payload unchanged.
+ * Set to {@code false} to render the bytes as base64.
+ * @param rawBytes false to encode as base64.
+ */
+ public void setRawBytes(boolean rawBytes) {
+ this.rawBytes = rawBytes;
+ }
+
+ /**
+ * Set to true to make the header name pattern match case-sensitive.
+ * Default false.
+ * @param caseSensitive true to make case-sensitive.
+ */
+ public void setCaseSensitive(boolean caseSensitive) {
+ this.caseSensitive = caseSensitive;
+ }
+
+ public Collection getHeaderPatterns() {
+ return Arrays.asList(this.headerPatterns);
+ }
+
+ @Override
+ public byte[] fromMessage(Message> message) {
+ Map headersToEncode =
+ this.allHeaders
+ ? message.getHeaders()
+ : pruneHeaders(message.getHeaders());
+
+ if (this.rawBytes && message.getPayload() instanceof byte[] bytes) {
+ return fromBytesPayload(bytes, headersToEncode);
+ }
+ else {
+ Message> messageToEncode = message;
+
+ if (!this.allHeaders) {
+ if (!headersToEncode.containsKey(MessageHeaders.ID)) {
+ headersToEncode.put(MessageHeaders.ID, MessageHeaders.ID_VALUE_NONE);
+ }
+ if (!headersToEncode.containsKey(MessageHeaders.TIMESTAMP)) {
+ headersToEncode.put(MessageHeaders.TIMESTAMP, -1L);
+ }
+
+ messageToEncode = new MutableMessage<>(message.getPayload(), headersToEncode);
+ }
+
+ try {
+ return this.objectMapper.writeValueAsBytes(messageToEncode);
+ }
+ catch (JacksonException ex) {
+ throw new UncheckedIOException(new IOException(ex));
+ }
+ }
+ }
+
+ private Map pruneHeaders(MessageHeaders messageHeaders) {
+ return messageHeaders
+ .entrySet()
+ .stream()
+ .filter(e -> matchHeader(e.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private boolean matchHeader(String header) {
+ return Boolean.TRUE.equals(this.caseSensitive
+ ? PatternMatchUtils.smartMatch(header, this.headerPatterns)
+ : PatternMatchUtils.smartMatchIgnoreCase(header, this.headerPatterns));
+ }
+
+ private byte[] fromBytesPayload(byte[] payload, Map headersToEncode) {
+ try {
+ byte[] headers = this.objectMapper.writeValueAsBytes(headersToEncode);
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[8 + headers.length + payload.length]);
+ buffer.putInt(headers.length);
+ buffer.put(headers);
+ buffer.putInt(payload.length);
+ buffer.put(payload);
+ return buffer.array();
+ }
+ catch (JacksonException ex) {
+ throw new UncheckedIOException(new IOException(ex));
+ }
+ }
+
+ @Override
+ public Message> toMessage(byte[] bytes, @Nullable Map headers) {
+ Message> message = null;
+ try {
+ message = decodeNativeFormat(bytes, headers);
+ }
+ catch (@SuppressWarnings("unused") Exception ex) {
+ this.logger.debug("Failed to decode native format", ex);
+ }
+ if (message == null) {
+ try {
+ message = (Message>) this.objectMapper.readValue(bytes, Object.class);
+ }
+ catch (Exception ex) {
+ this.logger.debug("Failed to decode JSON", ex);
+ }
+ }
+ if (message != null) {
+ return message;
+ }
+ else {
+ return headers == null ? new GenericMessage<>(bytes) : new GenericMessage<>(bytes, headers);
+ }
+ }
+
+ @Nullable
+ private Message> decodeNativeFormat(byte[] bytes, @Nullable Map headersToAdd) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ if (buffer.remaining() > 4) {
+ int headersLen = buffer.getInt();
+ if (headersLen >= 0 && headersLen < buffer.remaining() - 4) {
+ ((Buffer) buffer).position(headersLen + 4);
+ int payloadLen = buffer.getInt();
+ if (payloadLen != buffer.remaining()) {
+ return null;
+ }
+ else {
+ ((Buffer) buffer).position(4);
+ @SuppressWarnings("unchecked")
+ Map headers = this.objectMapper.readValue(bytes, buffer.position(), headersLen,
+ Map.class);
+
+ ((Buffer) buffer).position(buffer.position() + headersLen);
+ buffer.getInt();
+ Object payload;
+ byte[] payloadBytes = new byte[payloadLen];
+ buffer.get(payloadBytes);
+ payload = payloadBytes;
+
+ if (headersToAdd != null) {
+ headersToAdd.forEach(headers::putIfAbsent);
+ }
+
+ return new GenericMessage<>(payload, new MutableMessageHeaders(headers));
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJsonAccessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJacksonAccessorTests.java
similarity index 82%
rename from spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJsonAccessorTests.java
rename to spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJacksonAccessorTests.java
index 12078e41a5..2183277c1e 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJsonAccessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/json/AbstractJacksonAccessorTests.java
@@ -18,14 +18,14 @@
import java.util.List;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.node.ArrayNode;
+import tools.jackson.databind.node.ObjectNode;
+import tools.jackson.databind.node.StringNode;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.Expression;
@@ -34,24 +34,25 @@
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
-import org.springframework.integration.json.JsonPropertyAccessor.ArrayNodeAsList;
-import org.springframework.integration.json.JsonPropertyAccessor.ComparableJsonNode;
+import org.springframework.integration.json.JacksonPropertyAccessor.ArrayNodeAsList;
+import org.springframework.integration.json.JacksonPropertyAccessor.ComparableJsonNode;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
- * Abstract base class for tests involving {@link JsonPropertyAccessor} and {@link JsonIndexAccessor}.
+ * Abstract base class for tests involving {@link JacksonPropertyAccessor} and {@link JacksonIndexAccessor}.
*
* @author Eric Bottard
* @author Artem Bilan
* @author Paul Martin
* @author Pierre Lakreb
* @author Sam Brannen
+ * @author Jooyoung Pyoung
*
* @since 3.0
*/
-abstract class AbstractJsonAccessorTests {
+public abstract class AbstractJacksonAccessorTests {
protected final SpelExpressionParser parser = new SpelExpressionParser();
@@ -62,7 +63,7 @@ abstract class AbstractJsonAccessorTests {
@BeforeEach
void setUpEvaluationContext() {
DefaultConversionService conversionService = new DefaultConversionService();
- conversionService.addConverter(new JsonNodeWrapperToJsonNodeConverter());
+ conversionService.addConverter(new JsonNodeWrapperConverter());
context.setTypeConverter(new StandardTypeConverter(conversionService));
}
@@ -74,26 +75,26 @@ void setUpEvaluationContext() {
class JsonNodeTests {
@Test
- void textNode() throws Exception {
- TextNode json = (TextNode) mapper.readTree("\"foo\"");
+ void textNode() {
+ StringNode json = (StringNode) mapper.readTree("\"foo\"");
String result = evaluate(json, "#root", String.class);
assertThat(result).isEqualTo("\"foo\"");
}
@Test
- void nullProperty() throws Exception {
+ void nullProperty() {
JsonNode json = mapper.readTree("{\"foo\": null}");
assertThat(evaluate(json, "foo", String.class)).isNull();
}
@Test
- void missingProperty() throws Exception {
+ void missingProperty() {
JsonNode json = mapper.readTree(FOO_BAR_JSON);
assertThat(evaluate(json, "fizz", String.class)).isNull();
}
@Test
- void propertyLookup() throws Exception {
+ void propertyLookup() {
JsonNode json1 = mapper.readTree(FOO_BAR_JSON);
String value1 = evaluate(json1, "foo", String.class);
assertThat(value1).isEqualTo("bar");
@@ -106,7 +107,7 @@ void propertyLookup() throws Exception {
void arrayLookupWithIntegerIndexAndExplicitWrapping() throws Exception {
ArrayNode json = (ArrayNode) mapper.readTree("[3, 4, 5]");
// Have to wrap the root array because ArrayNode itself is not a List
- Integer actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Integer.class);
+ Integer actual = evaluate(JacksonPropertyAccessor.wrap(json), "[1]", Integer.class);
assertThat(actual).isEqualTo(4);
}
@@ -114,19 +115,19 @@ void arrayLookupWithIntegerIndexAndExplicitWrapping() throws Exception {
void arrayLookupWithIntegerIndexForNullValueAndExplicitWrapping() throws Exception {
ArrayNode json = (ArrayNode) mapper.readTree("[3, null, 5]");
// Have to wrap the root array because ArrayNode itself is not a List
- Integer actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Integer.class);
+ Integer actual = evaluate(JacksonPropertyAccessor.wrap(json), "[1]", Integer.class);
assertThat(actual).isNull();
}
@Test
- void arrayLookupWithNegativeIntegerIndex() throws Exception {
+ void arrayLookupWithNegativeIntegerIndex() {
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
// ArrayNodeAsList allows one to index into a JSON array via a negative index.
assertThat(evaluate(json, "foo[-1]", Integer.class)).isEqualTo(5);
}
@Test
- void arrayLookupWithNegativeIntegerIndexGreaterThanArrayLength() throws Exception {
+ void arrayLookupWithNegativeIntegerIndexGreaterThanArrayLength() {
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
// Although ArrayNodeAsList allows one to index into a JSON array via a negative
// index, if the result of (array.length - index) is still negative, Jackson's
@@ -135,14 +136,14 @@ void arrayLookupWithNegativeIntegerIndexGreaterThanArrayLength() throws Exceptio
}
@Test
- void arrayLookupWithNegativeIntegerIndexForNullValue() throws Exception {
+ void arrayLookupWithNegativeIntegerIndexForNullValue() {
JsonNode json = mapper.readTree("{\"foo\": [3, 4, null]}");
// ArrayNodeAsList allows one to index into a JSON array via a negative index.
assertThat(evaluate(json, "foo[-1]", Integer.class)).isNull();
}
@Test
- void arrayLookupWithIntegerIndexOutOfBounds() throws Exception {
+ void arrayLookupWithIntegerIndexOutOfBounds() {
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(() -> evaluate(json, "foo[3]", Object.class))
@@ -150,7 +151,7 @@ void arrayLookupWithIntegerIndexOutOfBounds() throws Exception {
}
@Test
- void arrayLookupWithStringIndex() throws Exception {
+ void arrayLookupWithStringIndex() {
JsonNode json = mapper.readTree("[3, 4, 5]");
Integer actual = evaluate(json, "['1']", Integer.class);
assertThat(actual).isEqualTo(4);
@@ -159,13 +160,12 @@ void arrayLookupWithStringIndex() throws Exception {
@Test
void nestedArrayLookupWithIntegerIndexAndExplicitWrapping() throws Exception {
ArrayNode json = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
- // JsonNode actual = evaluate(json, "1.1", JsonNode.class); // Does not work
- Object actual = evaluate(JsonPropertyAccessor.wrap(json), "[1][1]", Object.class);
+ Object actual = evaluate(JacksonPropertyAccessor.wrap(json), "[1][1]", Object.class);
assertThat(actual).isEqualTo(5);
}
@Test
- void nestedArrayLookupWithStringIndex() throws Exception {
+ void nestedArrayLookupWithStringIndex() {
JsonNode json = mapper.readTree("[[3], [4, 5], []]");
Integer actual = evaluate(json, "['1']['1']", Integer.class);
assertThat(actual).isEqualTo(5);
@@ -173,7 +173,7 @@ void nestedArrayLookupWithStringIndex() throws Exception {
@Test
@SuppressWarnings("unchecked")
- void nestedArrayLookupWithStringIndexAndThenIntegerIndex() throws Exception {
+ void nestedArrayLookupWithStringIndexAndThenIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
List list = evaluate(arrayNode, "['0']", List.class);
@@ -188,7 +188,7 @@ void nestedArrayLookupWithStringIndexAndThenIntegerIndex() throws Exception {
}
@Test
- void arrayProjection() throws Exception {
+ void arrayProjection() {
JsonNode json = mapper.readTree(FOO_BAR_ARRAY_FIZZ_JSON);
// Filter the bar array to return only the fizz value of each element (to prove that SpEL considers bar
@@ -201,7 +201,7 @@ void arrayProjection() throws Exception {
}
@Test
- void arraySelection() throws Exception {
+ void arraySelection() {
JsonNode json = mapper.readTree(FOO_BAR_ARRAY_FIZZ_JSON);
// Filter bar objects so that none match
@@ -221,7 +221,7 @@ void arraySelection() throws Exception {
}
@Test
- void nestedPropertyAccessViaJsonNode() throws Exception {
+ void nestedPropertyAccessViaJsonNode() {
JsonNode json = mapper.readTree(FOO_BAR_FIZZ_JSON);
assertThat(evaluate(json, "foo.bar", Integer.class)).isEqualTo(4);
@@ -229,7 +229,7 @@ void nestedPropertyAccessViaJsonNode() throws Exception {
}
@Test
- void noNullPointerExceptionWithCachedReadAccessor() throws Exception {
+ void noNullPointerExceptionWithCachedReadAccessor() {
Expression expression = parser.parseExpression("foo");
JsonNode json1 = mapper.readTree(FOO_BAR_JSON);
String value1 = expression.getValue(context, json1, String.class);
@@ -255,7 +255,7 @@ void selectorAccess() {
}
@Test
- void nestedPropertyAccessViaJsonAsString() throws Exception {
+ void nestedPropertyAccessViaJsonAsString() {
String json = FOO_BAR_FIZZ_JSON;
assertThat(evaluate(json, "foo.bar", Integer.class)).isEqualTo(4);
@@ -263,34 +263,34 @@ void nestedPropertyAccessViaJsonAsString() throws Exception {
}
@Test
- void jsonGetValueConversionAsJsonNode() throws Exception {
+ void jsonGetValueConversionAsJsonNode() {
// use JsonNode conversion
JsonNode node = evaluate(PROPERTY_NAMES_JSON, "property.^[name == 'value1']", JsonNode.class);
assertThat(node).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
}
@Test
- void jsonGetValueConversionAsObjectNode() throws Exception {
+ void jsonGetValueConversionAsObjectNode() {
// use ObjectNode conversion
ObjectNode node = evaluate(PROPERTY_NAMES_JSON, "property.^[name == 'value1']", ObjectNode.class);
assertThat(node).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
}
@Test
- void jsonGetValueConversionAsArrayNode() throws Exception {
+ void jsonGetValueConversionAsArrayNode() {
// use ArrayNode conversion
ArrayNode node = evaluate(PROPERTY_NAMES_JSON, "property", ArrayNode.class);
assertThat(node).isEqualTo(mapper.readTree("[{\"name\":\"value1\"},{\"name\":\"value2\"}]"));
}
@Test
- void comparingArrayNode() throws Exception {
+ void comparingArrayNode() {
Boolean actual = evaluate(PROPERTIES_WITH_NAMES_JSON, "property1 eq property2", Boolean.class);
assertThat(actual).isTrue();
}
@Test
- void comparingJsonNode() throws Exception {
+ void comparingJsonNode() {
Boolean actual = evaluate(PROPERTIES_WITH_NAMES_JSON, "property1[0] eq property2[0]", Boolean.class);
assertThat(actual).isTrue();
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonIndexAccessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JacksonIndexAccessorTests.java
similarity index 70%
rename from spring-integration-core/src/test/java/org/springframework/integration/json/JsonIndexAccessorTests.java
rename to spring-integration-core/src/test/java/org/springframework/integration/json/JacksonIndexAccessorTests.java
index c8e64fd96e..2f43114a4c 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonIndexAccessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JacksonIndexAccessorTests.java
@@ -18,73 +18,76 @@
import java.util.List;
-import com.fasterxml.jackson.databind.node.ArrayNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import tools.jackson.databind.node.ArrayNode;
-import org.springframework.integration.json.JsonPropertyAccessor.ArrayNodeAsList;
+import org.springframework.integration.json.JacksonPropertyAccessor.ArrayNodeAsList;
import static org.assertj.core.api.Assertions.assertThat;
/**
- * Tests for {@link JsonIndexAccessor} combined with {@link JsonPropertyAccessor}.
+ * Tests for {@link JacksonIndexAccessor} combined with {@link JacksonPropertyAccessor}.
*
* @author Sam Brannen
+ * @author Jooyoung Pyoung
+ *
* @since 6.4
- * @see JsonPropertyAccessorTests
+ *
+ * @see JacksonPropertyAccessorTests
*/
-class JsonIndexAccessorTests extends AbstractJsonAccessorTests {
+class JacksonIndexAccessorTests extends AbstractJacksonAccessorTests {
@BeforeEach
void registerJsonAccessors() {
- context.addIndexAccessor(new JsonIndexAccessor());
- // We also register a JsonPropertyAccessor to ensure that the JsonIndexAccessor
- // does not interfere with the feature set of the JsonPropertyAccessor.
- context.addPropertyAccessor(new JsonPropertyAccessor());
+ context.addIndexAccessor(new JacksonIndexAccessor());
+ // We also register a JacksonPropertyAccessor to ensure that the JacksonIndexAccessor
+ // does not interfere with the feature set of the JacksonPropertyAccessor.
+ context.addPropertyAccessor(new JacksonPropertyAccessor());
}
/**
* Tests which index directly into a Jackson {@link ArrayNode}, which is only supported
- * by {@link JsonIndexAccessor}.
+ * by {@link JacksonPropertyAccessor}.
*/
@Nested
class ArrayNodeTests {
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndex() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
Integer actual = evaluate(arrayNode, "[1]", Integer.class);
assertThat(actual).isEqualTo(4);
}
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, null, 5]");
Integer actual = evaluate(arrayNode, "[1]", Integer.class);
assertThat(actual).isNull();
}
@Test
- void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() throws Exception {
+ void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
Integer actual = evaluate(arrayNode, "[-1]", Integer.class);
- // JsonIndexAccessor allows one to index into a JSON array via a negative index.
+ // JacksonIndexAccessor allows one to index into a JSON array via a negative index.
assertThat(actual).isEqualTo(5);
}
@Test
- void indexDirectlyIntoArrayNodeWithNegativeIntegerIndexGreaterThanArrayLength() throws Exception {
+ void indexDirectlyIntoArrayNodeWithNegativeIntegerIndexGreaterThanArrayLength() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
Integer actual = evaluate(arrayNode, "[-99]", Integer.class);
- // Although JsonIndexAccessor allows one to index into a JSON array via a negative
+ // Although JacksonIndexAccessor allows one to index into a JSON array via a negative
// index, if the result of (array.length - index) is still negative, Jackson's
// ArrayNode.get() method returns null instead of throwing an IndexOutOfBoundsException.
assertThat(actual).isNull();
}
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
Integer actual = evaluate(arrayNode, "[9999]", Integer.class);
// Jackson's ArrayNode.get() method always returns null instead of throwing an IndexOutOfBoundsException.
@@ -92,11 +95,11 @@ void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() throws Exception {
}
/**
- * @see AbstractJsonAccessorTests.JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
+ * @see JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
*/
@Test
@SuppressWarnings("unchecked")
- void nestedArrayLookupsWithIntegerIndexes() throws Exception {
+ void nestedArrayLookupsWithIntegerIndexes() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
List list = evaluate(arrayNode, "[0]", List.class);
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JacksonPropertyAccessorTests.java
similarity index 77%
rename from spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java
rename to spring-integration-core/src/test/java/org/springframework/integration/json/JacksonPropertyAccessorTests.java
index 3644353d11..6bbfedef6f 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JacksonPropertyAccessorTests.java
@@ -16,10 +16,10 @@
package org.springframework.integration.json;
-import com.fasterxml.jackson.databind.node.ArrayNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import tools.jackson.databind.node.ArrayNode;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
@@ -28,60 +28,62 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
- * Tests for {@link JsonPropertyAccessor}.
+ * Tests for {@link JacksonPropertyAccessor}.
*
* @author Eric Bottard
* @author Artem Bilan
* @author Paul Martin
* @author Pierre Lakreb
* @author Sam Brannen
+ * @author Jooyoung Pyoung
*
* @since 3.0
- * @see JsonIndexAccessorTests
+ *
+ * @see JacksonIndexAccessorTests
*/
-class JsonPropertyAccessorTests extends AbstractJsonAccessorTests {
+public class JacksonPropertyAccessorTests extends AbstractJacksonAccessorTests {
@BeforeEach
void registerJsonPropertyAccessor() {
- context.addPropertyAccessor(new JsonPropertyAccessor());
+ context.addPropertyAccessor(new JacksonPropertyAccessor());
}
/**
* Tests which index directly into a Jackson {@link ArrayNode}, which is not supported
- * by {@link JsonPropertyAccessor}.
+ * by {@link JacksonPropertyAccessor}.
*/
@Nested
class ArrayNodeTests {
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndex() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
assertIndexingNotSupported(arrayNode, "[1]");
}
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, null, 5]");
assertIndexingNotSupported(arrayNode, "[1]");
}
@Test
- void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() throws Exception {
+ void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
assertIndexingNotSupported(arrayNode, "[-1]");
}
@Test
- void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() throws Exception {
+ void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
assertIndexingNotSupported(arrayNode, "[9999]");
}
/**
- * @see AbstractJsonAccessorTests.JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
+ * @see JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
*/
@Test
- void nestedArrayLookupWithIntegerIndexAndThenIntegerIndex() throws Exception {
+ void nestedArrayLookupWithIntegerIndexAndThenIntegerIndex() {
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
assertIndexingNotSupported(arrayNode, "[1][1]");
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapperTests.java b/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapperTests.java
new file mode 100644
index 0000000000..b882323bd9
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapperTests.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.support.json;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.GenericMessage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Jooyoung Pyoung
+ *
+ * @since 7.0
+ */
+public class EmbeddedHeadersJsonMessageMapperTests {
+
+ @Test
+ public void testEmbedAll() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper();
+ GenericMessage message = new GenericMessage<>("foo");
+ assertThat(mapper.toMessage(mapper.fromMessage(message))).isEqualTo(message);
+ }
+
+ @Test
+ public void testEmbedSome() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper(MessageHeaders.ID);
+ GenericMessage message = new GenericMessage<>("foo");
+ byte[] encodedMessage = mapper.fromMessage(message);
+ Message> decoded = mapper.toMessage(encodedMessage);
+ assertThat(decoded.getPayload()).isEqualTo(message.getPayload());
+ assertThat(decoded.getHeaders().getTimestamp()).isNotEqualTo(message.getHeaders().getTimestamp());
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map encodedMessageToCheck =
+ objectMapper.readValue(encodedMessage, new TypeReference<>() {
+
+ });
+
+ Object headers = encodedMessageToCheck.get("headers");
+ assertThat(headers).isNotNull();
+ assertThat(headers).isInstanceOf(Map.class);
+
+ @SuppressWarnings("unchecked")
+ Map headersToCheck = (Map) headers;
+ assertThat(headersToCheck).doesNotContainKey(MessageHeaders.TIMESTAMP);
+ }
+
+ @Test
+ public void testBytesEmbedAll() throws Exception {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper();
+ GenericMessage message = new GenericMessage<>("foo".getBytes());
+ Thread.sleep(2);
+ byte[] bytes = mapper.fromMessage(message);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ int headerLen = bb.getInt();
+ byte[] headerBytes = new byte[headerLen];
+ bb.get(headerBytes);
+ String headers = new String(headerBytes);
+ assertThat(headers).contains(message.getHeaders().getId().toString());
+ assertThat(headers).contains(String.valueOf(message.getHeaders().getTimestamp()));
+ assertThat(bb.getInt()).isEqualTo(3);
+ assertThat(bb.remaining()).isEqualTo(3);
+ assertThat((char) bb.get()).isEqualTo('f');
+ assertThat((char) bb.get()).isEqualTo('o');
+ assertThat((char) bb.get()).isEqualTo('o');
+ }
+
+ @Test
+ public void testBytesEmbedSome() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper("I*");
+ GenericMessage message = new GenericMessage<>("foo".getBytes(), Collections.singletonMap("bar", "baz"));
+ byte[] bytes = mapper.fromMessage(message);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ int headerLen = bb.getInt();
+ byte[] headerBytes = new byte[headerLen];
+ bb.get(headerBytes);
+ String headers = new String(headerBytes);
+ assertThat(headers).contains(message.getHeaders().getId().toString());
+ assertThat(headers).doesNotContain(MessageHeaders.TIMESTAMP);
+ assertThat(headers).doesNotContain("bar");
+ assertThat(bb.getInt()).isEqualTo(3);
+ assertThat(bb.remaining()).isEqualTo(3);
+ assertThat((char) bb.get()).isEqualTo('f');
+ assertThat((char) bb.get()).isEqualTo('o');
+ assertThat((char) bb.get()).isEqualTo('o');
+ }
+
+ @Test
+ public void testBytesEmbedAllJson() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper();
+ mapper.setRawBytes(false);
+ GenericMessage message = new GenericMessage<>("foo".getBytes());
+ byte[] mappedBytes = mapper.fromMessage(message);
+ String mapped = new String(mappedBytes);
+ assertThat(mapped).contains("[B\",\"Zm9v");
+ @SuppressWarnings("unchecked")
+ Message decoded = (Message) mapper.toMessage(mappedBytes);
+ assertThat(new String(decoded.getPayload())).isEqualTo("foo");
+
+ }
+
+ @Test
+ public void testBytesDecodeAll() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper();
+ GenericMessage message = new GenericMessage<>("foo".getBytes());
+ Message> decoded = mapper.toMessage(mapper.fromMessage(message));
+ assertThat(decoded).isEqualTo(message);
+ }
+
+ @Test
+ public void testDontMapIdButOthers() {
+ EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper("!" + MessageHeaders.ID, "*");
+ GenericMessage message = new GenericMessage<>("foo", Collections.singletonMap("bar", "baz"));
+ byte[] encodedMessage = mapper.fromMessage(message);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map encodedMessageToCheck =
+ objectMapper.readValue(encodedMessage, new TypeReference<>() {
+
+ });
+
+ Object headers = encodedMessageToCheck.get("headers");
+ assertThat(headers).isNotNull();
+ assertThat(headers).isInstanceOf(Map.class);
+
+ @SuppressWarnings("unchecked")
+ Map headersToCheck = (Map) headers;
+ assertThat(headersToCheck).doesNotContainKey(MessageHeaders.ID);
+ assertThat(headersToCheck).containsKey(MessageHeaders.TIMESTAMP);
+ assertThat(headersToCheck).containsKey("bar");
+
+ Message> decoded = mapper.toMessage(mapper.fromMessage(message));
+ assertThat(decoded.getHeaders().getTimestamp()).isEqualTo(message.getHeaders().getTimestamp());
+ assertThat(decoded.getHeaders().getId()).isNotEqualTo(message.getHeaders().getId());
+ assertThat(decoded.getHeaders().get("bar")).isEqualTo("baz");
+ }
+
+}