Skip to content

Commit 0f54f68

Browse files
committed
PayloadArgumentResolver supports JsonView as well (through AbstractMessageConverter revision)
AbstractMessageConverter provides overloaded methods with a conversion hint, MappingJackson2MessageConverter takes that hint into account, and SimpMessagingTemplate transformes such a hint in the given headers map into an explicit argument invocation argument. Issue: SPR-13265
1 parent 24b1dc1 commit 0f54f68

File tree

12 files changed

+214
-113
lines changed

12 files changed

+214
-113
lines changed

spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.apache.commons.logging.Log;
2525
import org.apache.commons.logging.LogFactory;
2626

27-
import org.springframework.core.MethodParameter;
2827
import org.springframework.messaging.Message;
2928
import org.springframework.messaging.MessageHeaders;
3029
import org.springframework.messaging.support.MessageBuilder;
@@ -40,20 +39,11 @@
4039
*
4140
* @author Rossen Stoyanchev
4241
* @author Sebastien Deleuze
42+
* @author Juergen Hoeller
4343
* @since 4.0
4444
*/
4545
public abstract class AbstractMessageConverter implements MessageConverter {
4646

47-
/**
48-
* Name of the header that can be set to provide further information
49-
* ({@link MethodParameter} instance) about the origin of the payload (for
50-
* {@link #toMessage(Object, MessageHeaders)}) or about the target of the payload
51-
* ({@link #fromMessage(Message, Class)}).
52-
* @since 4.2
53-
*/
54-
public static final String METHOD_PARAMETER_HINT_HEADER = "methodParameterHint";
55-
56-
5747
protected final Log logger = LogFactory.getLog(getClass());
5848

5949
private final List<MimeType> supportedMimeTypes;
@@ -174,10 +164,27 @@ protected MimeType getDefaultContentType(Object payload) {
174164

175165
@Override
176166
public final Object fromMessage(Message<?> message, Class<?> targetClass) {
167+
return fromMessage(message, targetClass, null);
168+
}
169+
170+
/**
171+
* A variant of {@link #fromMessage(Message, Class)} which takes an extra
172+
* conversion context as an argument, allowing to take e.g. annotations
173+
* on a payload parameter into account.
174+
* @param message the input message
175+
* @param targetClass the target class for the conversion
176+
* @param conversionHint an extra object passed to the {@link MessageConverter},
177+
* e.g. the associated {@code MethodParameter} (may be {@code null}}
178+
* @return the result of the conversion, or {@code null} if the converter cannot
179+
* perform the conversion
180+
* @since 4.2
181+
* @see #fromMessage(Message, Class)
182+
*/
183+
public final Object fromMessage(Message<?> message, Class<?> targetClass, Object conversionHint) {
177184
if (!canConvertFrom(message, targetClass)) {
178185
return null;
179186
}
180-
return convertFromInternal(message, targetClass);
187+
return convertFromInternal(message, targetClass, conversionHint);
181188
}
182189

183190
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
@@ -186,13 +193,33 @@ protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
186193

187194
@Override
188195
public final Message<?> toMessage(Object payload, MessageHeaders headers) {
196+
return toMessage(payload, headers, null);
197+
}
198+
199+
/**
200+
* A variant of {@link #toMessage(Object, MessageHeaders)} which takes an extra
201+
* conversion context as an argument, allowing to take e.g. annotations
202+
* on a return type into account.
203+
* @param payload the Object to convert
204+
* @param headers optional headers for the message (may be {@code null})
205+
* @param conversionHint an extra object passed to the {@link MessageConverter},
206+
* e.g. the associated {@code MethodParameter} (may be {@code null}}
207+
* @return the new message, or {@code null} if the converter does not support the
208+
* Object type or the target media type
209+
* @since 4.2
210+
* @see #toMessage(Object, MessageHeaders)
211+
*/
212+
public final Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
189213
if (!canConvertTo(payload, headers)) {
190214
return null;
191215
}
192216

193-
payload = convertToInternal(payload, headers);
194-
MimeType mimeType = getDefaultContentType(payload);
217+
payload = convertToInternal(payload, headers, conversionHint);
218+
if (payload == null) {
219+
return null;
220+
}
195221

222+
MimeType mimeType = getDefaultContentType(payload);
196223
if (headers != null) {
197224
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
198225
if (accessor != null && accessor.isMutable()) {
@@ -244,13 +271,52 @@ protected MimeType getMimeType(MessageHeaders headers) {
244271

245272
/**
246273
* Convert the message payload from serialized form to an Object.
274+
* @param message the input message
275+
* @param targetClass the target class for the conversion
276+
* @param conversionHint an extra object passed to the {@link MessageConverter},
277+
* e.g. the associated {@code MethodParameter} (may be {@code null}}
278+
* @return the result of the conversion, or {@code null} if the converter cannot
279+
* perform the conversion
280+
* @since 4.2
247281
*/
248-
public abstract Object convertFromInternal(Message<?> message, Class<?> targetClass);
282+
@SuppressWarnings("deprecation")
283+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
284+
return convertFromInternal(message, targetClass);
285+
}
249286

287+
/**
288+
* Convert the payload object to serialized form.
289+
* @param payload the Object to convert
290+
* @param headers optional headers for the message (may be {@code null})
291+
* @param conversionHint an extra object passed to the {@link MessageConverter},
292+
* e.g. the associated {@code MethodParameter} (may be {@code null}}
293+
* @return the resulting payload for the message, or {@code null} if the converter
294+
* cannot perform the conversion
295+
* @since 4.2
296+
*/
297+
@SuppressWarnings("deprecation")
298+
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
299+
return convertToInternal(payload, headers);
300+
}
301+
302+
/**
303+
* Convert the message payload from serialized form to an Object.
304+
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
305+
* (which is also protected instead of public)
306+
*/
307+
@Deprecated
308+
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
309+
return null;
310+
}
250311

251312
/**
252313
* Convert the payload object to serialized form.
314+
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
315+
* (which is also protected instead of public)
253316
*/
254-
public abstract Object convertToInternal(Object payload, MessageHeaders headers);
317+
@Deprecated
318+
public Object convertToInternal(Object payload, MessageHeaders headers) {
319+
return null;
320+
}
255321

256322
}

spring-messaging/src/main/java/org/springframework/messaging/converter/ByteArrayMessageConverter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -29,24 +29,23 @@
2929
*/
3030
public class ByteArrayMessageConverter extends AbstractMessageConverter {
3131

32-
3332
public ByteArrayMessageConverter() {
3433
super(MimeTypeUtils.APPLICATION_OCTET_STREAM);
3534
}
3635

3736

3837
@Override
3938
protected boolean supports(Class<?> clazz) {
40-
return byte[].class == clazz;
39+
return (byte[].class == clazz);
4140
}
4241

4342
@Override
44-
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
43+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
4544
return message.getPayload();
4645
}
4746

4847
@Override
49-
public Object convertToInternal(Object payload, MessageHeaders headers) {
48+
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
5049
return payload;
5150
}
5251

spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,28 @@ protected boolean supports(Class<?> clazz) {
195195
}
196196

197197
@Override
198-
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
198+
@SuppressWarnings("deprecation")
199+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
199200
JavaType javaType = this.objectMapper.constructType(targetClass);
200201
Object payload = message.getPayload();
202+
Class<?> view = getSerializationView(conversionHint);
203+
// Note: in the view case, calling withType instead of forType for compatibility with Jackson <2.5
201204
try {
202205
if (payload instanceof byte[]) {
203-
return this.objectMapper.readValue((byte[]) payload, javaType);
206+
if (view != null) {
207+
return this.objectMapper.readerWithView(view).withType(javaType).readValue((byte[]) payload);
208+
}
209+
else {
210+
return this.objectMapper.readValue((byte[]) payload, javaType);
211+
}
204212
}
205213
else {
206-
return this.objectMapper.readValue((String) payload, javaType);
214+
if (view != null) {
215+
return this.objectMapper.readerWithView(view).withType(javaType).readValue(payload.toString());
216+
}
217+
else {
218+
return this.objectMapper.readValue(payload.toString(), javaType);
219+
}
207220
}
208221
}
209222
catch (IOException ex) {
@@ -212,15 +225,15 @@ public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
212225
}
213226

214227
@Override
215-
public Object convertToInternal(Object payload, MessageHeaders headers) {
228+
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
216229
try {
217-
Class<?> serializationView = getSerializationView(headers);
230+
Class<?> view = getSerializationView(conversionHint);
218231
if (byte[].class == getSerializedPayloadClass()) {
219232
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
220233
JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
221234
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
222-
if (serializationView != null) {
223-
this.objectMapper.writerWithView(serializationView).writeValue(generator, payload);
235+
if (view != null) {
236+
this.objectMapper.writerWithView(view).writeValue(generator, payload);
224237
}
225238
else {
226239
this.objectMapper.writeValue(generator, payload);
@@ -229,8 +242,8 @@ public Object convertToInternal(Object payload, MessageHeaders headers) {
229242
}
230243
else {
231244
Writer writer = new StringWriter();
232-
if (serializationView != null) {
233-
this.objectMapper.writerWithView(serializationView).writeValue(writer, payload);
245+
if (view != null) {
246+
this.objectMapper.writerWithView(view).writeValue(writer, payload);
234247
}
235248
else {
236249
this.objectMapper.writeValue(writer, payload);
@@ -244,20 +257,40 @@ public Object convertToInternal(Object payload, MessageHeaders headers) {
244257
return payload;
245258
}
246259

247-
private Class<?> getSerializationView(MessageHeaders headers) {
248-
MethodParameter returnType = (headers != null ?
249-
(MethodParameter) headers.get(METHOD_PARAMETER_HINT_HEADER) : null);
250-
if (returnType == null) {
251-
return null;
260+
/**
261+
* Determine a Jackson serialization view based on the given conversion hint.
262+
* @param conversionHint the conversion hint Object as passed into the
263+
* converter for the current conversion attempt
264+
* @return the serialization view class, or {@code null} if none
265+
*/
266+
protected Class<?> getSerializationView(Object conversionHint) {
267+
if (conversionHint instanceof MethodParameter) {
268+
MethodParameter methodParam = (MethodParameter) conversionHint;
269+
JsonView annotation = methodParam.getParameterAnnotation(JsonView.class);
270+
if (annotation == null) {
271+
annotation = methodParam.getMethodAnnotation(JsonView.class);
272+
if (annotation == null) {
273+
return null;
274+
}
275+
}
276+
return extractViewClass(annotation, conversionHint);
277+
}
278+
else if (conversionHint instanceof JsonView) {
279+
return extractViewClass((JsonView) conversionHint, conversionHint);
252280
}
253-
JsonView annotation = returnType.getMethodAnnotation(JsonView.class);
254-
if (annotation == null) {
281+
else if (conversionHint instanceof Class) {
282+
return (Class) conversionHint;
283+
}
284+
else {
255285
return null;
256286
}
287+
}
288+
289+
private Class<?> extractViewClass(JsonView annotation, Object conversionHint) {
257290
Class<?>[] classes = annotation.value();
258291
if (classes.length != 1) {
259292
throw new IllegalArgumentException(
260-
"@JsonView only supported for handler methods with exactly 1 class argument: " + returnType);
293+
"@JsonView only supported for handler methods with exactly 1 class argument: " + conversionHint);
261294
}
262295
return classes[0];
263296
}

spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ protected boolean supports(Class<?> clazz) {
134134
}
135135

136136
@Override
137-
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
137+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
138138
Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
139139
try {
140140
Source source = getSource(message.getPayload());
@@ -159,7 +159,7 @@ private Source getSource(Object payload) {
159159
}
160160

161161
@Override
162-
public Object convertToInternal(Object payload, MessageHeaders headers) {
162+
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
163163
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
164164
try {
165165
if (byte[].class == getSerializedPayloadClass()) {

spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -46,18 +46,18 @@ public StringMessageConverter(Charset defaultCharset) {
4646

4747
@Override
4848
protected boolean supports(Class<?> clazz) {
49-
return String.class == clazz;
49+
return (String.class == clazz);
5050
}
5151

5252
@Override
53-
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
53+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
5454
Charset charset = getContentTypeCharset(getMimeType(message.getHeaders()));
5555
Object payload = message.getPayload();
56-
return (payload instanceof String) ? payload : new String((byte[]) payload, charset);
56+
return (payload instanceof String ? payload : new String((byte[]) payload, charset));
5757
}
5858

5959
@Override
60-
public Object convertToInternal(Object payload, MessageHeaders headers) {
60+
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
6161
if (byte[].class == getSerializedPayloadClass()) {
6262
Charset charset = getContentTypeCharset(getMimeType(headers));
6363
payload = ((String) payload).getBytes(charset);

0 commit comments

Comments
 (0)