From 876c9694c4f5f01161a311e30c2ac2cdd37d0335 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Tue, 19 May 2015 12:40:02 +0200 Subject: [PATCH] Allow use of @JsonView on @MessageMapping methods Issue: SPR-12741 --- .../converter/AbstractMessageConverter.java | 14 ++- .../MappingJackson2MessageConverter.java | 35 ++++++- .../SendToMethodReturnValueHandler.java | 15 ++- .../SubscriptionMethodReturnValueHandler.java | 7 +- .../MappingJackson2MessageConverterTests.java | 67 +++++++++++++ .../SendToMethodReturnValueHandlerTests.java | 98 ++++++++++++++++++- ...criptionMethodReturnValueHandlerTests.java | 92 ++++++++++++++++- 7 files changed, 310 insertions(+), 18 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java index 3cef560277..6403e0709f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -38,13 +39,24 @@ import org.springframework.util.MimeType; * and MIME type. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 4.0 */ public abstract class AbstractMessageConverter implements MessageConverter { - protected final Log logger = LogFactory.getLog(getClass()); + /** + * Name of the header that can be set to provide further information + * ({@link MethodParameter} instance) about the origin of the payload (for + * {@link #toMessage(Object, MessageHeaders)}) or about the target of the payload + * ({@link #fromMessage(Message, Class)}). + * + * @since 4.2 + */ + public static final String METHOD_PARAMETER_HINT_HEADER = "methodParameterHint"; + protected final Log logger = LogFactory.getLog(getClass()); + private final List supportedMimeTypes; private ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver(); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java index 2ee1789484..1ed703680c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java @@ -24,6 +24,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; @@ -33,6 +34,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; @@ -212,16 +214,27 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { @Override public Object convertToInternal(Object payload, MessageHeaders headers) { try { + Class serializationView = getSerializationView(headers); if (byte[].class.equals(getSerializedPayloadClass())) { ByteArrayOutputStream out = new ByteArrayOutputStream(1024); JsonEncoding encoding = getJsonEncoding(getMimeType(headers)); JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding); - this.objectMapper.writeValue(generator, payload); + if (serializationView != null) { + this.objectMapper.writerWithView(serializationView).writeValue(generator, payload); + } + else { + this.objectMapper.writeValue(generator, payload); + } payload = out.toByteArray(); } else { Writer writer = new StringWriter(); - this.objectMapper.writeValue(writer, payload); + if (serializationView != null) { + this.objectMapper.writerWithView(serializationView).writeValue(writer, payload); + } + else { + this.objectMapper.writeValue(writer, payload); + } payload = writer.toString(); } } @@ -231,6 +244,24 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { return payload; } + private Class getSerializationView(MessageHeaders headers) { + MethodParameter returnType = (headers == null ? null : + (MethodParameter)headers.get(METHOD_PARAMETER_HINT_HEADER)); + if (returnType == null) { + return null; + } + JsonView annotation = returnType.getMethodAnnotation(JsonView.class); + if (annotation == null) { + return null; + } + Class[] classes = annotation.value(); + if (classes.length != 1) { + throw new IllegalArgumentException( + "@JsonView only supported for handler methods with exactly 1 class argument: " + returnType); + } + return classes[0]; + } + /** * Determine the JSON encoding to use for the given content type. * @param contentType the MIME type from the MessageHeaders, if any diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java index e83506b8af..2305b799a5 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java @@ -25,6 +25,7 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; import org.springframework.messaging.handler.DestinationPatternsMessageCondition; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver; @@ -51,6 +52,7 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; * the message is sent to each destination. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 4.0 */ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueHandler { @@ -162,10 +164,10 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH for (String destination : destinations) { destination = this.placeholderHelper.replacePlaceholders(destination, varResolver); if (broadcast) { - this.messagingTemplate.convertAndSendToUser(user, destination, returnValue); + this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(null, returnType)); } else { - this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(sessionId)); + this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(sessionId, returnType)); } } } @@ -174,7 +176,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH String[] destinations = getTargetDestinations(sendTo, message, this.defaultDestinationPrefix); for (String destination : destinations) { destination = this.placeholderHelper.replacePlaceholders(destination, varResolver); - this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId)); + this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, returnType)); } } } @@ -210,12 +212,15 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH new String[] {defaultPrefix + destination} : new String[] {defaultPrefix + "/" + destination}); } - private MessageHeaders createHeaders(String sessionId) { + private MessageHeaders createHeaders(String sessionId, MethodParameter returnType) { SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); if (getHeaderInitializer() != null) { getHeaderInitializer().initHeaders(headerAccessor); } - headerAccessor.setSessionId(sessionId); + if (sessionId != null) { + headerAccessor.setSessionId(sessionId); + } + headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType); headerAccessor.setLeaveMutable(true); return headerAccessor.getMessageHeaders(); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java index 8317e5ffc4..e825122736 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; import org.springframework.messaging.core.MessageSendingOperations; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; @@ -44,6 +45,7 @@ import org.springframework.util.Assert; * input message. The message is then sent directly back to the connected client. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 4.0 */ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler { @@ -109,16 +111,17 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn logger.debug("Reply to @SubscribeMapping: " + returnValue); } - this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, subscriptionId)); + this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, subscriptionId, returnType)); } - private MessageHeaders createHeaders(String sessionId, String subscriptionId) { + private MessageHeaders createHeaders(String sessionId, String subscriptionId, MethodParameter returnType) { SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); if (getHeaderInitializer() != null) { getHeaderInitializer().initHeaders(headerAccessor); } headerAccessor.setSessionId(sessionId); headerAccessor.setSubscriptionId(subscriptionId); + headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType); headerAccessor.setLeaveMutable(true); return headerAccessor.getMessageHeaders(); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java index 0faeca6066..7c46a272c1 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java @@ -17,14 +17,17 @@ package org.springframework.messaging.converter; import java.io.IOException; +import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.DeserializationFeature; import org.junit.Test; +import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -174,6 +177,23 @@ public class MappingJackson2MessageConverterTests { assertEquals(contentType, message.getHeaders().get(MessageHeaders.CONTENT_TYPE)); } + @Test + public void jsonView() throws Exception { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + + Map map = new HashMap<>(); + Method method = this.getClass().getDeclaredMethod("handle"); + MethodParameter returnType = new MethodParameter(method, -1); + map.put(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType); + MessageHeaders headers = new MessageHeaders(map); + Message message = converter.toMessage(handle(), headers); + String actual = new String((byte[]) message.getPayload(), UTF_8); + + assertThat(actual, containsString("\"withView1\":\"with\"")); + assertThat(actual, not(containsString("\"withView2\":\"with\""))); + assertThat(actual, not(containsString("\"withoutView\":\"without\""))); + } + public static class MyBean { @@ -238,4 +258,51 @@ public class MappingJackson2MessageConverterTests { } } + public interface MyJacksonView1 {}; + public interface MyJacksonView2 {}; + + public static class JacksonViewBean { + + @JsonView(MyJacksonView1.class) + private String withView1; + + @JsonView(MyJacksonView2.class) + private String withView2; + + private String withoutView; + + public String getWithView1() { + return withView1; + } + + public void setWithView1(String withView1) { + this.withView1 = withView1; + } + + public String getWithView2() { + return withView2; + } + + public void setWithView2(String withView2) { + this.withView2 = withView2; + } + + public String getWithoutView() { + return withoutView; + } + + public void setWithoutView(String withoutView) { + this.withoutView = withoutView; + } + } + + @JsonView(MyJacksonView1.class) + public JacksonViewBean handle() { + JacksonViewBean bean = new JacksonViewBean(); + bean.setWithView1("with"); + bean.setWithView2("with"); + bean.setWithoutView("without"); + return bean; + } + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java index dd07cc36db..d28b851c1d 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java @@ -16,6 +16,7 @@ package org.springframework.messaging.simp.annotation.support; +import com.fasterxml.jackson.annotation.JsonView; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; import static org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER; @@ -23,6 +24,7 @@ import static org.springframework.messaging.support.MessageHeaderAccessor.*; import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.LinkedHashMap; import java.util.Map; @@ -41,6 +43,8 @@ import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.handler.DestinationPatternsMessageCondition; import org.springframework.messaging.handler.annotation.SendTo; @@ -57,6 +61,7 @@ import org.springframework.util.MimeType; * Test fixture for {@link SendToMethodReturnValueHandlerTests}. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze */ public class SendToMethodReturnValueHandlerTests { @@ -69,18 +74,21 @@ public class SendToMethodReturnValueHandlerTests { private SendToMethodReturnValueHandler handlerAnnotationNotRequired; + private SendToMethodReturnValueHandler jsonHandler; + @Mock private MessageChannel messageChannel; - @Captor ArgumentCaptor> messageCaptor; + @Captor private ArgumentCaptor> messageCaptor; private MethodParameter noAnnotationsReturnType; private MethodParameter sendToReturnType; private MethodParameter sendToDefaultDestReturnType; - private MethodParameter sendToWithPlaceholdersType; + private MethodParameter sendToWithPlaceholdersReturnType; private MethodParameter sendToUserReturnType; private MethodParameter sendToUserSingleSessionReturnType; private MethodParameter sendToUserDefaultDestReturnType; private MethodParameter sendToUserSingleSessionDefaultDestReturnType; + private MethodParameter jsonViewReturnType; @Before @@ -90,10 +98,13 @@ public class SendToMethodReturnValueHandlerTests { SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel); messagingTemplate.setMessageConverter(new StringMessageConverter()); - this.handler = new SendToMethodReturnValueHandler(messagingTemplate, true); this.handlerAnnotationNotRequired = new SendToMethodReturnValueHandler(messagingTemplate, false); + SimpMessagingTemplate jsonMessagingTemplate = new SimpMessagingTemplate(this.messageChannel); + jsonMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter()); + this.jsonHandler = new SendToMethodReturnValueHandler(jsonMessagingTemplate, true); + Method method = this.getClass().getDeclaredMethod("handleNoAnnotations"); this.noAnnotationsReturnType = new MethodParameter(method, -1); @@ -104,7 +115,7 @@ public class SendToMethodReturnValueHandlerTests { this.sendToReturnType = new MethodParameter(method, -1); method = this.getClass().getDeclaredMethod("handleAndSendToWithPlaceholders"); - this.sendToWithPlaceholdersType = new MethodParameter(method, -1); + this.sendToWithPlaceholdersReturnType = new MethodParameter(method, -1); method = this.getClass().getDeclaredMethod("handleAndSendToUser"); this.sendToUserReturnType = new MethodParameter(method, -1); @@ -117,6 +128,9 @@ public class SendToMethodReturnValueHandlerTests { method = this.getClass().getDeclaredMethod("handleAndSendToUserDefaultDestinationSingleSession"); this.sendToUserSingleSessionDefaultDestReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToJsonView"); + this.jsonViewReturnType = new MethodParameter(method, -1); } @@ -143,6 +157,7 @@ public class SendToMethodReturnValueHandlerTests { assertEquals("/topic/dest", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.noAnnotationsReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -161,12 +176,14 @@ public class SendToMethodReturnValueHandlerTests { assertEquals("/dest1", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); accessor = getCapturedAccessor(1); assertEquals(sessionId, accessor.getSessionId()); assertEquals("/dest2", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -185,6 +202,7 @@ public class SendToMethodReturnValueHandlerTests { assertEquals("/topic/dest", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToDefaultDestReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -220,6 +238,7 @@ public class SendToMethodReturnValueHandlerTests { assertTrue(accessor.isMutable()); assertEquals("sess1", accessor.getSessionId()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.noAnnotationsReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -261,7 +280,7 @@ public class SendToMethodReturnValueHandlerTests { accessor.setSubscriptionId("sub1"); accessor.setHeader(DESTINATION_TEMPLATE_VARIABLES_HEADER, vars); Message message = MessageBuilder.createMessage(PAYLOAD, accessor.getMessageHeaders()); - this.handler.handleReturnValue(PAYLOAD, this.sendToWithPlaceholdersType, message); + this.handler.handleReturnValue(PAYLOAD, this.sendToWithPlaceholdersReturnType, message); verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); @@ -287,12 +306,14 @@ public class SendToMethodReturnValueHandlerTests { assertEquals(MIME_TYPE, accessor.getContentType()); assertEquals("/user/" + user.getName() + "/dest1", accessor.getDestination()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); accessor = getCapturedAccessor(1); assertEquals(sessionId, accessor.getSessionId()); assertEquals("/user/" + user.getName() + "/dest2", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -364,6 +385,7 @@ public class SendToMethodReturnValueHandlerTests { assertEquals("/user/" + user.getName() + "/queue/dest", accessor.getDestination()); assertEquals(MIME_TYPE, accessor.getContentType()); assertNull("Subscription id should not be copied", accessor.getSubscriptionId()); + assertEquals(this.sendToUserSingleSessionDefaultDestReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -386,6 +408,22 @@ public class SendToMethodReturnValueHandlerTests { assertEquals("sess1", accessor.getSessionId()); } + @Test + public void jsonView() throws Exception { + + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null); + this.jsonHandler.handleReturnValue(handleAndSendToJsonView(), this.jsonViewReturnType, inputMessage); + + verify(this.messageChannel).send(this.messageCaptor.capture()); + Message message = this.messageCaptor.getValue(); + assertNotNull(message); + + assertEquals("{\"withView1\":\"with\"}", new String((byte[])message.getPayload(), StandardCharsets.UTF_8)); + } + private Message createInputMessage(String sessId, String subsId, String destinationPrefix, String destination, Principal principal) { @@ -475,4 +513,54 @@ public class SendToMethodReturnValueHandlerTests { return PAYLOAD; } + @SuppressWarnings("unused") + @SendTo({"/dest"}) + @JsonView(MyJacksonView1.class) + public JacksonViewBean handleAndSendToJsonView() { + JacksonViewBean payload = new JacksonViewBean(); + payload.setWithView1("with"); + payload.setWithView2("with"); + payload.setWithoutView("without"); + return payload; + } + + + private interface MyJacksonView1 {}; + private interface MyJacksonView2 {}; + + private static class JacksonViewBean { + + @JsonView(MyJacksonView1.class) + private String withView1; + + @JsonView(MyJacksonView2.class) + private String withView2; + + private String withoutView; + + public String getWithView1() { + return withView1; + } + + public void setWithView1(String withView1) { + this.withView1 = withView1; + } + + public String getWithView2() { + return withView2; + } + + public void setWithView2(String withView2) { + this.withView2 = withView2; + } + + public String getWithoutView() { + return withoutView; + } + + public void setWithoutView(String withoutView) { + this.withoutView = withoutView; + } + } + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java index 618a1598a8..3ad80bd9b0 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -18,8 +18,10 @@ package org.springframework.messaging.simp.annotation.support; import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Principal; +import com.fasterxml.jackson.annotation.JsonView; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -32,6 +34,8 @@ import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.core.MessageSendingOperations; import org.springframework.messaging.handler.annotation.MessageMapping; @@ -50,6 +54,7 @@ import static org.mockito.BDDMockito.*; * Test fixture for {@link SubscriptionMethodReturnValueHandler}. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze */ public class SubscriptionMethodReturnValueHandlerTests { @@ -60,9 +65,11 @@ public class SubscriptionMethodReturnValueHandlerTests { private SubscriptionMethodReturnValueHandler handler; + private SubscriptionMethodReturnValueHandler jsonHandler; + @Mock private MessageChannel messageChannel; - @Captor ArgumentCaptor> messageCaptor; + @Captor private ArgumentCaptor> messageCaptor; private MethodParameter subscribeEventReturnType; @@ -70,6 +77,8 @@ public class SubscriptionMethodReturnValueHandlerTests { private MethodParameter messageMappingReturnType; + private MethodParameter subscribeEventJsonViewReturnType; + @Before public void setup() throws Exception { @@ -78,9 +87,12 @@ public class SubscriptionMethodReturnValueHandlerTests { SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel); messagingTemplate.setMessageConverter(new StringMessageConverter()); - this.handler = new SubscriptionMethodReturnValueHandler(messagingTemplate); + SimpMessagingTemplate jsonMessagingTemplate = new SimpMessagingTemplate(this.messageChannel); + jsonMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter()); + this.jsonHandler = new SubscriptionMethodReturnValueHandler(jsonMessagingTemplate); + Method method = this.getClass().getDeclaredMethod("getData"); this.subscribeEventReturnType = new MethodParameter(method, -1); @@ -89,6 +101,9 @@ public class SubscriptionMethodReturnValueHandlerTests { method = this.getClass().getDeclaredMethod("handle"); this.messageMappingReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("getJsonView"); + this.subscribeEventJsonViewReturnType = new MethodParameter(method, -1); } @@ -123,6 +138,7 @@ public class SubscriptionMethodReturnValueHandlerTests { assertEquals(subscriptionId, headerAccessor.getSubscriptionId()); assertEquals(destination, headerAccessor.getDestination()); assertEquals(MIME_TYPE, headerAccessor.getContentType()); + assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); } @Test @@ -149,6 +165,26 @@ public class SubscriptionMethodReturnValueHandlerTests { assertTrue(headerAccessor.isMutable()); assertEquals(sessionId, headerAccessor.getSessionId()); assertEquals(subscriptionId, headerAccessor.getSubscriptionId()); + assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER)); + } + + @Test + public void testJsonView() throws Exception { + + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + String subscriptionId = "subs1"; + String destination = "/dest"; + Message inputMessage = createInputMessage(sessionId, subscriptionId, destination, null); + + this.jsonHandler.handleReturnValue(getJsonView(), this.subscribeEventJsonViewReturnType, inputMessage); + + verify(this.messageChannel).send(this.messageCaptor.capture()); + Message message = this.messageCaptor.getValue(); + assertNotNull(message); + + assertEquals("{\"withView1\":\"with\"}", new String((byte[])message.getPayload(), StandardCharsets.UTF_8)); } @@ -177,4 +213,54 @@ public class SubscriptionMethodReturnValueHandlerTests { public String handle() { return PAYLOAD; } + + @SubscribeMapping("/jsonview") // not needed for the tests but here for completeness + @JsonView(MyJacksonView1.class) + public JacksonViewBean getJsonView() { + JacksonViewBean payload = new JacksonViewBean(); + payload.setWithView1("with"); + payload.setWithView2("with"); + payload.setWithoutView("without"); + return payload; + } + + + private interface MyJacksonView1 {}; + private interface MyJacksonView2 {}; + + private static class JacksonViewBean { + + @JsonView(MyJacksonView1.class) + private String withView1; + + @JsonView(MyJacksonView2.class) + private String withView2; + + private String withoutView; + + public String getWithView1() { + return withView1; + } + + public void setWithView1(String withView1) { + this.withView1 = withView1; + } + + public String getWithView2() { + return withView2; + } + + public void setWithView2(String withView2) { + this.withView2 = withView2; + } + + public String getWithoutView() { + return withoutView; + } + + public void setWithoutView(String withoutView) { + this.withoutView = withoutView; + } + } + }