Skip to content

Commit f282dbd

Browse files
committed
Support generics in the JsonToObjectTransformer
* Deprecate `JsonObjectMapperAdapter` if favor of `default` methods in the `JsonObjectMapper` * Introduce `JsonObjectMapper.fromJson(Object, ResolvableType)` to support generics during deserialization * Add `JsonHeaders.RESOLVABLE_TYPE` header handling for the `ResolvableType` management * Add `ResolvableType` argument into the `JsonToObjectTransformer` * Change the `JsonToObjectTransformer` logic to consult request message headers first * Add `ResolvableType`-based factory method into the `Transformers` * Document the change
1 parent 3751505 commit f282dbd

File tree

13 files changed

+285
-107
lines changed

13 files changed

+285
-107
lines changed

spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 the original author or authors.
2+
* Copyright 2016-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121

2222
import org.reactivestreams.Publisher;
2323

24+
import org.springframework.core.ResolvableType;
2425
import org.springframework.core.convert.converter.Converter;
2526
import org.springframework.core.serializer.Deserializer;
2627
import org.springframework.core.serializer.Serializer;
@@ -161,15 +162,25 @@ else if (resultType != null) {
161162
}
162163

163164
public static JsonToObjectTransformer fromJson() {
164-
return fromJson(null, null);
165+
return fromJson((Class<?>) null, null);
165166
}
166167

167168
public static JsonToObjectTransformer fromJson(@Nullable Class<?> targetClass) {
168169
return fromJson(targetClass, null);
169170
}
170171

172+
/**
173+
* Construct a {@link JsonToObjectTransformer} based on the provided {@link ResolvableType}.
174+
* @param targetType the {@link ResolvableType} top use.
175+
* @return the {@link JsonToObjectTransformer} instance.
176+
* @since 5.2
177+
*/
178+
public static JsonToObjectTransformer fromJson(ResolvableType targetType) {
179+
return fromJson(targetType, null);
180+
}
181+
171182
public static JsonToObjectTransformer fromJson(@Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
172-
return fromJson(null, jsonObjectMapper);
183+
return fromJson((Class<?>) null, jsonObjectMapper);
173184
}
174185

175186
public static JsonToObjectTransformer fromJson(@Nullable Class<?> targetClass,
@@ -178,6 +189,20 @@ public static JsonToObjectTransformer fromJson(@Nullable Class<?> targetClass,
178189
return new JsonToObjectTransformer(targetClass, jsonObjectMapper);
179190
}
180191

192+
/**
193+
* Construct a {@link JsonToObjectTransformer} based on the provided {@link ResolvableType}
194+
* and {@link JsonObjectMapper}.
195+
* @param targetType the {@link ResolvableType} top use.
196+
* @param jsonObjectMapper the {@link JsonObjectMapper} top use.
197+
* @return the {@link JsonToObjectTransformer} instance.
198+
* @since 5.2
199+
*/
200+
public static JsonToObjectTransformer fromJson(ResolvableType targetType,
201+
@Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
202+
203+
return new JsonToObjectTransformer(targetType, jsonObjectMapper);
204+
}
205+
181206
public static PayloadSerializingTransformer serializer() {
182207
return serializer(null);
183208
}

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

Lines changed: 98 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
import java.io.UncheckedIOException;
2121

2222
import org.springframework.beans.factory.BeanClassLoaderAware;
23+
import org.springframework.core.ResolvableType;
2324
import org.springframework.integration.mapping.support.JsonHeaders;
24-
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
2525
import org.springframework.integration.support.json.JsonObjectMapper;
2626
import org.springframework.integration.support.json.JsonObjectMapperProvider;
2727
import org.springframework.integration.transformer.AbstractTransformer;
28+
import org.springframework.lang.Nullable;
2829
import org.springframework.messaging.Message;
30+
import org.springframework.messaging.MessageHeaders;
31+
import org.springframework.util.Assert;
32+
import org.springframework.util.ClassUtils;
2933

3034
/**
3135
* Transformer implementation that converts a JSON string payload into an instance of the
@@ -42,35 +46,60 @@
4246
*
4347
* @author Mark Fisher
4448
* @author Artem Bilan
49+
*
4550
* @see JsonObjectMapper
4651
* @see org.springframework.integration.support.json.JsonObjectMapperProvider
52+
*
4753
* @since 2.0
4854
*/
4955
public class JsonToObjectTransformer extends AbstractTransformer implements BeanClassLoaderAware {
5056

51-
private final Class<?> targetClass;
57+
private final ResolvableType targetType;
5258

5359
private final JsonObjectMapper<?, ?> jsonObjectMapper;
5460

61+
private ClassLoader classLoader;
62+
5563
public JsonToObjectTransformer() {
5664
this((Class<?>) null);
5765
}
5866

59-
public JsonToObjectTransformer(Class<?> targetClass) {
60-
this(targetClass, null);
67+
public JsonToObjectTransformer(@Nullable Class<?> targetClass) {
68+
this(ResolvableType.forClass(targetClass));
69+
}
70+
71+
/**
72+
* Construct an instance based on the provided {@link ResolvableType}.
73+
* @param targetType the {@link ResolvableType} to use.
74+
* @since 5.2
75+
*/
76+
public JsonToObjectTransformer(ResolvableType targetType) {
77+
this(targetType, null);
78+
}
79+
80+
public JsonToObjectTransformer(@Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
81+
this((Class<?>) null, jsonObjectMapper);
6182
}
6283

63-
public JsonToObjectTransformer(JsonObjectMapper<?, ?> jsonObjectMapper) {
64-
this(null, jsonObjectMapper);
84+
public JsonToObjectTransformer(@Nullable Class<?> targetClass, @Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
85+
this(ResolvableType.forClass(targetClass), jsonObjectMapper);
6586
}
6687

67-
public JsonToObjectTransformer(Class<?> targetClass, JsonObjectMapper<?, ?> jsonObjectMapper) {
68-
this.targetClass = targetClass;
88+
/**
89+
* Construct an instance based on the provided {@link ResolvableType} and {@link JsonObjectMapper}.
90+
* @param targetType the {@link ResolvableType} to use.
91+
* @param jsonObjectMapper the {@link JsonObjectMapper} to use.
92+
* @since 5.2
93+
*/
94+
public JsonToObjectTransformer(ResolvableType targetType, @Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
95+
Assert.notNull(targetType, "'targetType' must not be null");
96+
this.targetType = targetType;
6997
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JsonObjectMapperProvider.newInstance();
7098
}
7199

72100
@Override
73101
public void setBeanClassLoader(ClassLoader classLoader) {
102+
this.classLoader = classLoader;
74103
if (this.jsonObjectMapper instanceof BeanClassLoaderAware) {
75104
((BeanClassLoaderAware) this.jsonObjectMapper).setBeanClassLoader(classLoader);
76105
}
@@ -83,21 +112,72 @@ public String getComponentType() {
83112

84113
@Override
85114
protected Object doTransform(Message<?> message) {
115+
MessageHeaders headers = message.getHeaders();
116+
boolean removeHeaders = false;
117+
ResolvableType valueType = obtainResolvableTypeFromHeadersIfAny(headers);
118+
119+
if (valueType != null) {
120+
removeHeaders = true;
121+
}
122+
else {
123+
valueType = this.targetType;
124+
}
125+
126+
Object result;
86127
try {
87-
if (this.targetClass != null) {
88-
return this.jsonObjectMapper.fromJson(message.getPayload(), this.targetClass);
89-
}
90-
else {
91-
Object result = this.jsonObjectMapper.fromJson(message.getPayload(), message.getHeaders());
92-
AbstractIntegrationMessageBuilder<Object> messageBuilder = this.getMessageBuilderFactory().withPayload(result)
93-
.copyHeaders(message.getHeaders())
94-
.removeHeaders(JsonHeaders.HEADERS.toArray(new String[3]));
95-
return messageBuilder.build();
96-
}
128+
result = this.jsonObjectMapper.fromJson(message.getPayload(), valueType);
129+
97130
}
98131
catch (IOException e) {
99132
throw new UncheckedIOException(e);
100133
}
134+
135+
if (removeHeaders) {
136+
return getMessageBuilderFactory()
137+
.withPayload(result)
138+
.copyHeaders(headers)
139+
.removeHeaders(JsonHeaders.HEADERS.toArray(new String[3]))
140+
.build();
141+
}
142+
else {
143+
return result;
144+
}
145+
}
146+
147+
148+
private ResolvableType obtainResolvableTypeFromHeadersIfAny(MessageHeaders headers) {
149+
ResolvableType valueType = null;
150+
if (headers.containsKey(JsonHeaders.TYPE_ID) || headers.containsKey(JsonHeaders.RESOLVABLE_TYPE)) {
151+
valueType = headers.get(JsonHeaders.RESOLVABLE_TYPE, ResolvableType.class);
152+
if (valueType == null) {
153+
Class<?> targetClass = getClassForValue(headers.get(JsonHeaders.TYPE_ID));
154+
Class<?> contentClass = null;
155+
Class<?> keyClass = null;
156+
if (headers.containsKey(JsonHeaders.CONTENT_TYPE_ID)) {
157+
contentClass = getClassForValue(headers.get(JsonHeaders.CONTENT_TYPE_ID));
158+
}
159+
if (headers.containsKey(JsonHeaders.KEY_TYPE_ID)) {
160+
keyClass = getClassForValue(headers.get(JsonHeaders.KEY_TYPE_ID));
161+
}
162+
163+
valueType = JsonObjectMapper.buildResolvableType(targetClass, contentClass, keyClass);
164+
}
165+
}
166+
return valueType;
167+
}
168+
169+
private Class<?> getClassForValue(Object classValue) {
170+
if (classValue instanceof Class<?>) {
171+
return (Class<?>) classValue;
172+
}
173+
else {
174+
try {
175+
return ClassUtils.forName(classValue.toString(), this.classLoader);
176+
}
177+
catch (ClassNotFoundException | LinkageError e) {
178+
throw new IllegalStateException(e);
179+
}
180+
}
101181
}
102182

103183
}

spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2016 the original author or authors.
2+
* Copyright 2013-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,7 +42,14 @@ private JsonHeaders() {
4242

4343
public static final String KEY_TYPE_ID = PREFIX + "__KeyTypeId__";
4444

45+
/**
46+
* The header to represent a {@link org.springframework.core.ResolvableType}
47+
* for the target deserialized object.
48+
* @since 5.2
49+
*/
50+
public static final String RESOLVABLE_TYPE = PREFIX + "_resolvableType";
51+
4552
public static final Collection<String> HEADERS =
46-
Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID));
53+
Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID, RESOLVABLE_TYPE));
4754

4855
}

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828

2929
import org.springframework.beans.factory.BeanClassLoaderAware;
30+
import org.springframework.core.ResolvableType;
3031
import org.springframework.util.ClassUtils;
3132

3233
/**
@@ -40,11 +41,10 @@
4041
*
4142
* @since 3.0
4243
*/
43-
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> extends JsonObjectMapperAdapter<N, P>
44-
implements BeanClassLoaderAware {
44+
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> implements JsonObjectMapper<N, P>, BeanClassLoaderAware {
4545

4646
protected static final Collection<Class<?>> supportedJsonTypes =
47-
Arrays.<Class<?>>asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class);
47+
Arrays.asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class);
4848

4949
private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
5050

@@ -59,13 +59,18 @@ protected ClassLoader getClassLoader() {
5959

6060
@Override
6161
public <T> T fromJson(Object json, Class<T> valueType) throws IOException {
62-
return fromJson(json, this.constructType(valueType));
62+
return fromJson(json, constructType(valueType));
63+
}
64+
65+
@Override
66+
public <T> T fromJson(Object json, ResolvableType valueType) throws IOException {
67+
return fromJson(json, constructType(valueType.getType()));
6368
}
6469

6570
@Override
6671
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws IOException {
6772
J javaType = extractJavaType(javaTypes);
68-
return this.fromJson(json, javaType);
73+
return fromJson(json, javaType);
6974
}
7075

7176
protected J createJavaType(Map<String, Object> javaTypes, String javaTypeKey) {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,12 @@
5252
* @deprecated since 5.2. Will be removed in the next version.
5353
*/
5454
@Deprecated
55-
public class BoonJsonObjectMapper extends JsonObjectMapperAdapter<Map<String, Object>, Object>
56-
implements BeanClassLoaderAware {
55+
public class BoonJsonObjectMapper implements JsonObjectMapper<Map<String, Object>, Object>, BeanClassLoaderAware {
5756

5857
private static final Log logger = LogFactory.getLog(BoonJsonObjectMapper.class);
5958

6059
private static final Collection<Class<?>> supportedJsonTypes =
61-
Arrays.<Class<?>>asList(String.class, byte[].class, byte[].class, File.class, InputStream.class, Reader.class);
60+
Arrays.asList(String.class, byte[].class, byte[].class, File.class, InputStream.class, Reader.class);
6261

6362

6463
private final ObjectMapper objectMapper;

0 commit comments

Comments
 (0)