Skip to content

Commit 876c969

Browse files
committed
Allow use of @JSONVIEW on @MessageMapping methods
Issue: SPR-12741
1 parent c36435c commit 876c969

File tree

7 files changed

+310
-18
lines changed

7 files changed

+310
-18
lines changed

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

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

27+
import org.springframework.core.MethodParameter;
2728
import org.springframework.messaging.Message;
2829
import org.springframework.messaging.MessageHeaders;
2930
import org.springframework.messaging.support.MessageBuilder;
@@ -38,13 +39,24 @@
3839
* and MIME type.
3940
*
4041
* @author Rossen Stoyanchev
42+
* @author Sebastien Deleuze
4143
* @since 4.0
4244
*/
4345
public abstract class AbstractMessageConverter implements MessageConverter {
4446

45-
protected final Log logger = LogFactory.getLog(getClass());
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+
*
53+
* @since 4.2
54+
*/
55+
public static final String METHOD_PARAMETER_HINT_HEADER = "methodParameterHint";
4656

4757

58+
protected final Log logger = LogFactory.getLog(getClass());
59+
4860
private final List<MimeType> supportedMimeTypes;
4961

5062
private ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Arrays;
2525
import java.util.concurrent.atomic.AtomicReference;
2626

27+
import com.fasterxml.jackson.annotation.JsonView;
2728
import com.fasterxml.jackson.core.JsonEncoding;
2829
import com.fasterxml.jackson.core.JsonGenerator;
2930
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
@@ -33,6 +34,7 @@
3334
import com.fasterxml.jackson.databind.ObjectMapper;
3435
import com.fasterxml.jackson.databind.SerializationFeature;
3536

37+
import org.springframework.core.MethodParameter;
3638
import org.springframework.messaging.Message;
3739
import org.springframework.messaging.MessageHeaders;
3840
import org.springframework.util.Assert;
@@ -212,16 +214,27 @@ public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
212214
@Override
213215
public Object convertToInternal(Object payload, MessageHeaders headers) {
214216
try {
217+
Class<?> serializationView = getSerializationView(headers);
215218
if (byte[].class.equals(getSerializedPayloadClass())) {
216219
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
217220
JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
218221
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
219-
this.objectMapper.writeValue(generator, payload);
222+
if (serializationView != null) {
223+
this.objectMapper.writerWithView(serializationView).writeValue(generator, payload);
224+
}
225+
else {
226+
this.objectMapper.writeValue(generator, payload);
227+
}
220228
payload = out.toByteArray();
221229
}
222230
else {
223231
Writer writer = new StringWriter();
224-
this.objectMapper.writeValue(writer, payload);
232+
if (serializationView != null) {
233+
this.objectMapper.writerWithView(serializationView).writeValue(writer, payload);
234+
}
235+
else {
236+
this.objectMapper.writeValue(writer, payload);
237+
}
225238
payload = writer.toString();
226239
}
227240
}
@@ -231,6 +244,24 @@ public Object convertToInternal(Object payload, MessageHeaders headers) {
231244
return payload;
232245
}
233246

247+
private Class<?> getSerializationView(MessageHeaders headers) {
248+
MethodParameter returnType = (headers == null ? null :
249+
(MethodParameter)headers.get(METHOD_PARAMETER_HINT_HEADER));
250+
if (returnType == null) {
251+
return null;
252+
}
253+
JsonView annotation = returnType.getMethodAnnotation(JsonView.class);
254+
if (annotation == null) {
255+
return null;
256+
}
257+
Class<?>[] classes = annotation.value();
258+
if (classes.length != 1) {
259+
throw new IllegalArgumentException(
260+
"@JsonView only supported for handler methods with exactly 1 class argument: " + returnType);
261+
}
262+
return classes[0];
263+
}
264+
234265
/**
235266
* Determine the JSON encoding to use for the given content type.
236267
* @param contentType the MIME type from the MessageHeaders, if any

spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.messaging.Message;
2626
import org.springframework.messaging.MessageChannel;
2727
import org.springframework.messaging.MessageHeaders;
28+
import org.springframework.messaging.converter.AbstractMessageConverter;
2829
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
2930
import org.springframework.messaging.handler.annotation.SendTo;
3031
import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
@@ -51,6 +52,7 @@
5152
* the message is sent to each destination.
5253
*
5354
* @author Rossen Stoyanchev
55+
* @author Sebastien Deleuze
5456
* @since 4.0
5557
*/
5658
public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@@ -162,10 +164,10 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType, Me
162164
for (String destination : destinations) {
163165
destination = this.placeholderHelper.replacePlaceholders(destination, varResolver);
164166
if (broadcast) {
165-
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue);
167+
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(null, returnType));
166168
}
167169
else {
168-
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(sessionId));
170+
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(sessionId, returnType));
169171
}
170172
}
171173
}
@@ -174,7 +176,7 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType, Me
174176
String[] destinations = getTargetDestinations(sendTo, message, this.defaultDestinationPrefix);
175177
for (String destination : destinations) {
176178
destination = this.placeholderHelper.replacePlaceholders(destination, varResolver);
177-
this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId));
179+
this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, returnType));
178180
}
179181
}
180182
}
@@ -210,12 +212,15 @@ protected String[] getTargetDestinations(Annotation annotation, Message<?> messa
210212
new String[] {defaultPrefix + destination} : new String[] {defaultPrefix + "/" + destination});
211213
}
212214

213-
private MessageHeaders createHeaders(String sessionId) {
215+
private MessageHeaders createHeaders(String sessionId, MethodParameter returnType) {
214216
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
215217
if (getHeaderInitializer() != null) {
216218
getHeaderInitializer().initHeaders(headerAccessor);
217219
}
218-
headerAccessor.setSessionId(sessionId);
220+
if (sessionId != null) {
221+
headerAccessor.setSessionId(sessionId);
222+
}
223+
headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
219224
headerAccessor.setLeaveMutable(true);
220225
return headerAccessor.getMessageHeaders();
221226
}

spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.core.MethodParameter;
2323
import org.springframework.messaging.Message;
2424
import org.springframework.messaging.MessageHeaders;
25+
import org.springframework.messaging.converter.AbstractMessageConverter;
2526
import org.springframework.messaging.core.MessageSendingOperations;
2627
import org.springframework.messaging.handler.annotation.SendTo;
2728
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
@@ -44,6 +45,7 @@
4445
* input message. The message is then sent directly back to the connected client.
4546
*
4647
* @author Rossen Stoyanchev
48+
* @author Sebastien Deleuze
4749
* @since 4.0
4850
*/
4951
public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@@ -109,16 +111,17 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType, Me
109111
logger.debug("Reply to @SubscribeMapping: " + returnValue);
110112
}
111113

112-
this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, subscriptionId));
114+
this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, subscriptionId, returnType));
113115
}
114116

115-
private MessageHeaders createHeaders(String sessionId, String subscriptionId) {
117+
private MessageHeaders createHeaders(String sessionId, String subscriptionId, MethodParameter returnType) {
116118
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
117119
if (getHeaderInitializer() != null) {
118120
getHeaderInitializer().initHeaders(headerAccessor);
119121
}
120122
headerAccessor.setSessionId(sessionId);
121123
headerAccessor.setSubscriptionId(subscriptionId);
124+
headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
122125
headerAccessor.setLeaveMutable(true);
123126
return headerAccessor.getMessageHeaders();
124127
}

spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717
package org.springframework.messaging.converter;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Method;
2021
import java.nio.charset.Charset;
2122
import java.util.Arrays;
2223
import java.util.HashMap;
2324
import java.util.Map;
2425

26+
import com.fasterxml.jackson.annotation.JsonView;
2527
import com.fasterxml.jackson.databind.DeserializationFeature;
2628
import org.junit.Test;
2729

30+
import org.springframework.core.MethodParameter;
2831
import org.springframework.messaging.Message;
2932
import org.springframework.messaging.MessageHeaders;
3033
import org.springframework.messaging.support.MessageBuilder;
@@ -174,6 +177,23 @@ public void toMessageUtf16String() {
174177
assertEquals(contentType, message.getHeaders().get(MessageHeaders.CONTENT_TYPE));
175178
}
176179

180+
@Test
181+
public void jsonView() throws Exception {
182+
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
183+
184+
Map<String, Object> map = new HashMap<>();
185+
Method method = this.getClass().getDeclaredMethod("handle");
186+
MethodParameter returnType = new MethodParameter(method, -1);
187+
map.put(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
188+
MessageHeaders headers = new MessageHeaders(map);
189+
Message<?> message = converter.toMessage(handle(), headers);
190+
String actual = new String((byte[]) message.getPayload(), UTF_8);
191+
192+
assertThat(actual, containsString("\"withView1\":\"with\""));
193+
assertThat(actual, not(containsString("\"withView2\":\"with\"")));
194+
assertThat(actual, not(containsString("\"withoutView\":\"without\"")));
195+
}
196+
177197

178198
public static class MyBean {
179199

@@ -238,4 +258,51 @@ public void setArray(String[] array) {
238258
}
239259
}
240260

261+
public interface MyJacksonView1 {};
262+
public interface MyJacksonView2 {};
263+
264+
public static class JacksonViewBean {
265+
266+
@JsonView(MyJacksonView1.class)
267+
private String withView1;
268+
269+
@JsonView(MyJacksonView2.class)
270+
private String withView2;
271+
272+
private String withoutView;
273+
274+
public String getWithView1() {
275+
return withView1;
276+
}
277+
278+
public void setWithView1(String withView1) {
279+
this.withView1 = withView1;
280+
}
281+
282+
public String getWithView2() {
283+
return withView2;
284+
}
285+
286+
public void setWithView2(String withView2) {
287+
this.withView2 = withView2;
288+
}
289+
290+
public String getWithoutView() {
291+
return withoutView;
292+
}
293+
294+
public void setWithoutView(String withoutView) {
295+
this.withoutView = withoutView;
296+
}
297+
}
298+
299+
@JsonView(MyJacksonView1.class)
300+
public JacksonViewBean handle() {
301+
JacksonViewBean bean = new JacksonViewBean();
302+
bean.setWithView1("with");
303+
bean.setWithView2("with");
304+
bean.setWithoutView("without");
305+
return bean;
306+
}
307+
241308
}

0 commit comments

Comments
 (0)