Skip to content

Commit 7e4b20e

Browse files
committed
GH-10304: Refine Jackson 3 classes to accept only JsonMapper instances
Fixes: #10304 Enhances type safety and consistency with Jackson 3 JSON-specific handling. * Replace `ObjectMapper` with `JsonMapper` across Spring Integration: - Core components: `EmbeddedHeadersJsonMessageMapper`, `JacksonJsonObjectMapper`, `JacksonPropertyAccessor`, `MessageJsonDeserializer` - Utilities: `JacksonMessagingUtils.messagingAwareMapper()` - Tests: Update test classes to use `JsonMapper` * Clean up unused `IOException` declaration in `EmbeddedHeadersJsonMessageMapper` Signed-off-by: Jooyoung Pyoung <[email protected]>
1 parent 0bd825a commit 7e4b20e

File tree

15 files changed

+80
-86
lines changed

15 files changed

+80
-86
lines changed

spring-integration-core/src/main/java/org/springframework/integration/json/JacksonPropertyAccessor.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.jspecify.annotations.Nullable;
2323
import tools.jackson.core.JacksonException;
2424
import tools.jackson.databind.JsonNode;
25-
import tools.jackson.databind.ObjectMapper;
2625
import tools.jackson.databind.json.JsonMapper;
2726
import tools.jackson.databind.node.ArrayNode;
2827
import tools.jackson.databind.node.NullNode;
@@ -55,13 +54,13 @@ public class JacksonPropertyAccessor implements PropertyAccessor {
5554
JsonNode.class
5655
};
5756

58-
private ObjectMapper objectMapper = JsonMapper.builder()
57+
private JsonMapper jsonMapper = JsonMapper.builder()
5958
.findAndAddModules(JacksonPropertyAccessor.class.getClassLoader())
6059
.build();
6160

62-
public void setObjectMapper(ObjectMapper objectMapper) {
63-
Assert.notNull(objectMapper, "'objectMapper' cannot be null");
64-
this.objectMapper = objectMapper;
61+
public void setObjectMapper(JsonMapper jsonMapper) {
62+
Assert.notNull(jsonMapper, "'jsonMapper' cannot be null");
63+
this.jsonMapper = jsonMapper;
6564
}
6665

6766
@Override
@@ -94,7 +93,7 @@ else if (target instanceof JsonNodeWrapper<?> jsonNodeWrapper) {
9493
}
9594
else if (target instanceof String content) {
9695
try {
97-
return this.objectMapper.readTree(content);
96+
return this.jsonMapper.readTree(content);
9897
}
9998
catch (JacksonException e) {
10099
throw new AccessException("Exception while trying to deserialize String", e);

spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedHeadersJsonMessageMapper.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.apache.commons.logging.LogFactory;
3030
import org.jspecify.annotations.Nullable;
3131
import tools.jackson.core.JacksonException;
32-
import tools.jackson.databind.ObjectMapper;
32+
import tools.jackson.databind.json.JsonMapper;
3333

3434
import org.springframework.integration.mapping.BytesMessageMapper;
3535
import org.springframework.integration.support.MutableMessage;
@@ -40,13 +40,13 @@
4040
import org.springframework.messaging.support.GenericMessage;
4141

4242
/**
43-
* For outbound messages, uses a message-aware Jackson object mapper to render the message
43+
* For outbound messages, uses a message-aware Jackson JSON mapper to render the message
4444
* as JSON. For messages with {@code byte[]} payloads, if rendered as JSON, Jackson
4545
* performs Base64 conversion on the bytes. If payload is {@code byte[]} and
4646
* the {@link #setRawBytes(boolean) rawBytes} property is true (default), the result has the form
4747
* {@code [headersLen][headers][payloadLen][payload]}; with the headers
4848
* rendered in JSON and the payload unchanged.
49-
* Otherwise, message is fully serialized and deserialized with Jackson object mapper.
49+
* Otherwise, message is fully serialized and deserialized with Jackson JSON mapper.
5050
* <p>
5151
* By default, all headers are included; you can provide simple patterns to specify a
5252
* subset of headers.
@@ -56,7 +56,7 @@
5656
* <p>
5757
* <b>IMPORTANT</b>
5858
* <p>
59-
* The default object mapper will only deserialize classes in certain packages.
59+
* The default JSON mapper will only deserialize classes in certain packages.
6060
*
6161
* <pre class=code>
6262
* "java.util",
@@ -69,10 +69,10 @@
6969
* "org.springframework.integration.handler"
7070
* </pre>
7171
* <p>
72-
* To add more packages, create an object mapper using
72+
* To add more packages, create an JSON mapper using
7373
* {@link JacksonMessagingUtils#messagingAwareMapper(String...)}.
7474
* <p>
75-
* A constructor is provided allowing the provision of such a configured object mapper.
75+
* A constructor is provided allowing the provision of such a configured JSON mapper.
7676
*
7777
* @author Jooyoung Pyoung
7878
*
@@ -82,7 +82,7 @@ public class EmbeddedHeadersJsonMessageMapper implements BytesMessageMapper {
8282

8383
protected final Log logger = LogFactory.getLog(getClass());
8484

85-
private final ObjectMapper objectMapper;
85+
private final JsonMapper jsonMapper;
8686

8787
private final String[] headerPatterns;
8888

@@ -113,20 +113,20 @@ public EmbeddedHeadersJsonMessageMapper(String... headerPatterns) {
113113
/**
114114
* Construct an instance that embeds all headers, using the
115115
* supplied JSON object mapper.
116-
* @param objectMapper the object mapper.
116+
* @param jsonMapper the JSON mapper.
117117
*/
118-
public EmbeddedHeadersJsonMessageMapper(ObjectMapper objectMapper) {
119-
this(objectMapper, "*");
118+
public EmbeddedHeadersJsonMessageMapper(JsonMapper jsonMapper) {
119+
this(jsonMapper, "*");
120120
}
121121

122122
/**
123123
* Construct an instance that embeds headers matching the supplied patterns using the
124124
* supplied JSON object mapper.
125-
* @param objectMapper the object mapper.
125+
* @param jsonMapper the JSON mapper.
126126
* @param headerPatterns the patterns.
127127
*/
128-
public EmbeddedHeadersJsonMessageMapper(ObjectMapper objectMapper, String... headerPatterns) {
129-
this.objectMapper = objectMapper;
128+
public EmbeddedHeadersJsonMessageMapper(JsonMapper jsonMapper, String... headerPatterns) {
129+
this.jsonMapper = jsonMapper;
130130
this.headerPatterns = Arrays.copyOf(headerPatterns, headerPatterns.length);
131131
this.allHeaders = this.headerPatterns.length == 1 && this.headerPatterns[0].equals("*");
132132
}
@@ -181,7 +181,7 @@ public byte[] fromMessage(Message<?> message) {
181181
}
182182

183183
try {
184-
return this.objectMapper.writeValueAsBytes(messageToEncode);
184+
return this.jsonMapper.writeValueAsBytes(messageToEncode);
185185
}
186186
catch (JacksonException ex) {
187187
throw new UncheckedIOException(new IOException(ex));
@@ -205,7 +205,7 @@ private boolean matchHeader(String header) {
205205

206206
private byte[] fromBytesPayload(byte[] payload, Map<String, Object> headersToEncode) {
207207
try {
208-
byte[] headers = this.objectMapper.writeValueAsBytes(headersToEncode);
208+
byte[] headers = this.jsonMapper.writeValueAsBytes(headersToEncode);
209209
ByteBuffer buffer = ByteBuffer.wrap(new byte[8 + headers.length + payload.length]);
210210
buffer.putInt(headers.length);
211211
buffer.put(headers);
@@ -229,7 +229,7 @@ public Message<?> toMessage(byte[] bytes, @Nullable Map<String, Object> headers)
229229
}
230230
if (message == null) {
231231
try {
232-
message = (Message<?>) this.objectMapper.readValue(bytes, Object.class);
232+
message = (Message<?>) this.jsonMapper.readValue(bytes, Object.class);
233233
}
234234
catch (Exception ex) {
235235
this.logger.debug("Failed to decode JSON", ex);
@@ -244,7 +244,7 @@ public Message<?> toMessage(byte[] bytes, @Nullable Map<String, Object> headers)
244244
}
245245

246246
@Nullable
247-
private Message<?> decodeNativeFormat(byte[] bytes, @Nullable Map<String, Object> headersToAdd) throws IOException {
247+
private Message<?> decodeNativeFormat(byte[] bytes, @Nullable Map<String, Object> headersToAdd) {
248248
ByteBuffer buffer = ByteBuffer.wrap(bytes);
249249
if (buffer.remaining() > 4) {
250250
int headersLen = buffer.getInt();
@@ -257,7 +257,7 @@ private Message<?> decodeNativeFormat(byte[] bytes, @Nullable Map<String, Object
257257
else {
258258
((Buffer) buffer).position(4);
259259
@SuppressWarnings("unchecked")
260-
Map<String, Object> headers = this.objectMapper.readValue(bytes, buffer.position(), headersLen,
260+
Map<String, Object> headers = this.jsonMapper.readValue(bytes, buffer.position(), headersLen,
261261
Map.class);
262262

263263
((Buffer) buffer).position(buffer.position() + headersLen);

spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonObjectMapper.java

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import tools.jackson.databind.DeserializationFeature;
3232
import tools.jackson.databind.JavaType;
3333
import tools.jackson.databind.JsonNode;
34-
import tools.jackson.databind.ObjectMapper;
3534
import tools.jackson.databind.json.JsonMapper;
3635

3736
import org.springframework.integration.mapping.support.JsonHeaders;
@@ -41,7 +40,7 @@
4140
* Jackson 3 JSON-processor (@link https://github.com/FasterXML)
4241
* {@linkplain JsonObjectMapper} implementation.
4342
* Delegates {@link #toJson} and {@link #fromJson}
44-
* to the {@linkplain ObjectMapper}
43+
* to the {@linkplain JsonMapper}
4544
* <p>
4645
* It customizes Jackson's default properties with the following ones:
4746
* <ul>
@@ -57,28 +56,28 @@
5756
*/
5857
public class JacksonJsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonNode, JsonParser, JavaType> {
5958

60-
private final ObjectMapper objectMapper;
59+
private final JsonMapper jsonMapper;
6160

6261
public JacksonJsonObjectMapper() {
63-
this.objectMapper = JsonMapper.builder()
62+
this.jsonMapper = JsonMapper.builder()
6463
.findAndAddModules(JacksonJsonObjectMapper.class.getClassLoader())
6564
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
6665
.build();
6766
}
6867

69-
public JacksonJsonObjectMapper(ObjectMapper objectMapper) {
70-
Assert.notNull(objectMapper, "objectMapper must not be null");
71-
this.objectMapper = objectMapper;
68+
public JacksonJsonObjectMapper(JsonMapper jsonMapper) {
69+
Assert.notNull(jsonMapper, "jsonMapper must not be null");
70+
this.jsonMapper = jsonMapper;
7271
}
7372

74-
public ObjectMapper getObjectMapper() {
75-
return this.objectMapper;
73+
public JsonMapper getObjectMapper() {
74+
return this.jsonMapper;
7675
}
7776

7877
@Override
7978
public String toJson(Object value) throws IOException {
8079
try {
81-
return this.objectMapper.writeValueAsString(value);
80+
return this.jsonMapper.writeValueAsString(value);
8281
}
8382
catch (JacksonException e) {
8483
throw new IOException(e);
@@ -88,7 +87,7 @@ public String toJson(Object value) throws IOException {
8887
@Override
8988
public void toJson(Object value, Writer writer) throws IOException {
9089
try {
91-
this.objectMapper.writeValue(writer, value);
90+
this.jsonMapper.writeValue(writer, value);
9291
}
9392
catch (JacksonException e) {
9493
throw new IOException(e);
@@ -99,33 +98,33 @@ public void toJson(Object value, Writer writer) throws IOException {
9998
public JsonNode toJsonNode(Object json) throws IOException {
10099
try {
101100
if (json instanceof String) {
102-
return this.objectMapper.readTree((String) json);
101+
return this.jsonMapper.readTree((String) json);
103102
}
104103
else if (json instanceof byte[]) {
105-
return this.objectMapper.readTree((byte[]) json);
104+
return this.jsonMapper.readTree((byte[]) json);
106105
}
107106
else if (json instanceof File) {
108-
return this.objectMapper.readTree((File) json);
107+
return this.jsonMapper.readTree((File) json);
109108
}
110109
else if (json instanceof URL) {
111-
return this.objectMapper.readTree((URL) json);
110+
return this.jsonMapper.readTree((URL) json);
112111
}
113112
else if (json instanceof InputStream) {
114-
return this.objectMapper.readTree((InputStream) json);
113+
return this.jsonMapper.readTree((InputStream) json);
115114
}
116115
else if (json instanceof Reader) {
117-
return this.objectMapper.readTree((Reader) json);
116+
return this.jsonMapper.readTree((Reader) json);
118117
}
119118
}
120119
catch (JacksonException e) {
121120
if (!(json instanceof String) && !(json instanceof byte[])) {
122121
throw new IOException(e);
123122
}
124-
// Otherwise the input might not be valid JSON, fallback to TextNode with ObjectMapper.valueToTree()
123+
// Otherwise the input might not be valid JSON, fallback to TextNode with JsonMapper.valueToTree()
125124
}
126125

127126
try {
128-
return this.objectMapper.valueToTree(json);
127+
return this.jsonMapper.valueToTree(json);
129128
}
130129
catch (JacksonException e) {
131130
throw new IOException(e);
@@ -136,22 +135,22 @@ else if (json instanceof Reader) {
136135
protected <T> T fromJson(Object json, JavaType type) throws IOException {
137136
try {
138137
if (json instanceof String) {
139-
return this.objectMapper.readValue((String) json, type);
138+
return this.jsonMapper.readValue((String) json, type);
140139
}
141140
else if (json instanceof byte[]) {
142-
return this.objectMapper.readValue((byte[]) json, type);
141+
return this.jsonMapper.readValue((byte[]) json, type);
143142
}
144143
else if (json instanceof File) {
145-
return this.objectMapper.readValue((File) json, type);
144+
return this.jsonMapper.readValue((File) json, type);
146145
}
147146
else if (json instanceof URL) {
148-
return this.objectMapper.readValue((URL) json, type);
147+
return this.jsonMapper.readValue((URL) json, type);
149148
}
150149
else if (json instanceof InputStream) {
151-
return this.objectMapper.readValue((InputStream) json, type);
150+
return this.jsonMapper.readValue((InputStream) json, type);
152151
}
153152
else if (json instanceof Reader) {
154-
return this.objectMapper.readValue((Reader) json, type);
153+
return this.jsonMapper.readValue((Reader) json, type);
155154
}
156155
else {
157156
throw new IllegalArgumentException("'json' argument must be an instance of: " + SUPPORTED_JSON_TYPES
@@ -166,7 +165,7 @@ else if (json instanceof Reader) {
166165
@Override
167166
public <T> T fromJson(JsonParser parser, Type valueType) throws IOException {
168167
try {
169-
return this.objectMapper.readValue(parser, constructType(valueType));
168+
return this.jsonMapper.readValue(parser, constructType(valueType));
170169
}
171170
catch (JacksonException e) {
172171
throw new IOException(e);
@@ -183,19 +182,19 @@ protected JavaType extractJavaType(Map<String, Object> javaTypes) {
183182

184183
JavaType contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
185184
if (classType.getKeyType() == null) {
186-
return this.objectMapper.getTypeFactory()
185+
return this.jsonMapper.getTypeFactory()
187186
.constructCollectionType((Class<? extends Collection<?>>) classType.getRawClass(),
188187
contentClassType);
189188
}
190189

191190
JavaType keyClassType = createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
192-
return this.objectMapper.getTypeFactory()
191+
return this.jsonMapper.getTypeFactory()
193192
.constructMapType((Class<? extends Map<?, ?>>) classType.getRawClass(), keyClassType, contentClassType);
194193
}
195194

196195
@Override
197196
protected JavaType constructType(Type type) {
198-
return this.objectMapper.constructType(type);
197+
return this.jsonMapper.constructType(type);
199198
}
200199

201200
}

spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonMessagingUtils.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import tools.jackson.databind.DatabindContext;
3030
import tools.jackson.databind.DefaultTyping;
3131
import tools.jackson.databind.JavaType;
32-
import tools.jackson.databind.ObjectMapper;
3332
import tools.jackson.databind.cfg.MapperConfig;
3433
import tools.jackson.databind.json.JsonMapper;
3534
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
@@ -45,7 +44,7 @@
4544
import org.springframework.messaging.support.GenericMessage;
4645

4746
/**
48-
* Utility for creating Jackson {@link ObjectMapper} instance for Spring messaging.
47+
* Utility for creating Jackson {@link JsonMapper} instance for Spring messaging.
4948
*
5049
* <p>Provides custom serializers/deserializers for Spring messaging types
5150
* and validates deserialization against trusted package patterns.
@@ -75,15 +74,15 @@ private JacksonMessagingUtils() {
7574
}
7675

7776
/**
78-
* Return an {@link ObjectMapper} if available,
77+
* Return an {@link JsonMapper} if available,
7978
* supplied with Message specific serializers and deserializers.
8079
* Also configured to store typo info in the {@code @class} property.
8180
* @param trustedPackages the trusted Java packages for deserialization.
82-
* @return the mapper.
81+
* @return the JSON mapper.
8382
* @throws IllegalStateException if an implementation is not available.
8483
* @since 7.0
8584
*/
86-
public static ObjectMapper messagingAwareMapper(String @Nullable ... trustedPackages) {
85+
public static JsonMapper messagingAwareMapper(String @Nullable ... trustedPackages) {
8786
if (JacksonPresent.isJackson3Present()) {
8887
GenericMessageJsonDeserializer genericMessageDeserializer = new GenericMessageJsonDeserializer();
8988
ErrorMessageJsonDeserializer errorMessageDeserializer = new ErrorMessageJsonDeserializer();
@@ -98,7 +97,7 @@ public static ObjectMapper messagingAwareMapper(String @Nullable ... trustedPack
9897
.addDeserializer(AdviceMessage.class, adviceMessageDeserializer)
9998
.addDeserializer(MutableMessage.class, mutableMessageDeserializer);
10099

101-
ObjectMapper mapper = JsonMapper.builder()
100+
JsonMapper mapper = JsonMapper.builder()
102101
.findAndAddModules(JacksonMessagingUtils.class.getClassLoader())
103102
.setDefaultTyping(new AllowListTypeResolverBuilder(trustedPackages))
104103
.addModules(simpleModule)

0 commit comments

Comments
 (0)