diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java index d13658cc9e8..eec040b77a9 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -25,8 +25,8 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes import org.springframework.util.ClassUtils; /** - * A {@link HandlerMethodArgumentResolver} for {@link Message} parameters. - * Validates that the generic type of the payload matches with the message value. + * {@code HandlerMethodArgumentResolver} for {@link Message} method arguments. + * Validates that the generic type of the payload matches to the message value. * * @author Rossen Stoyanchev * @author Stephane Nicoll @@ -43,16 +43,17 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol public Object resolveArgument(MethodParameter parameter, Message message) throws Exception { Class paramType = parameter.getParameterType(); if (!paramType.isAssignableFrom(message.getClass())) { - throw new MethodArgumentTypeMismatchException(message, parameter, - "The actual message type [" + ClassUtils.getQualifiedName(message.getClass()) + "] " + - "does not match the expected type [" + ClassUtils.getQualifiedName(paramType) + "]"); + String actual = ClassUtils.getQualifiedName(message.getClass()); + String expected = ClassUtils.getQualifiedName(paramType); + throw new MethodArgumentTypeMismatchException(message, parameter, "The actual message type " + + "[" + actual + "] does not match the expected type [" + expected + "]"); } - Class expectedPayloadType = getPayloadType(parameter); + Class targetPayloadType = getPayloadType(parameter); Object payload = message.getPayload(); - if (payload != null && expectedPayloadType != null && !expectedPayloadType.isInstance(payload)) { + if (payload != null && !targetPayloadType.isInstance(payload)) { throw new MethodArgumentTypeMismatchException(message, parameter, - "The expected Message payload type [" + ClassUtils.getQualifiedName(expectedPayloadType) + + "The expected Message payload type [" + ClassUtils.getQualifiedName(targetPayloadType) + "] does not match the actual payload type [" + ClassUtils.getQualifiedName(payload.getClass()) + "]"); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java index 099fa1ae6f4..cb218c34a32 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -111,9 +111,13 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver { return payload; } else { - payload = (this.converter instanceof SmartMessageConverter ? - ((SmartMessageConverter) this.converter).fromMessage(message, targetClass, parameter) : - this.converter.fromMessage(message, targetClass)); + if (this.converter instanceof SmartMessageConverter) { + SmartMessageConverter smartConverter = (SmartMessageConverter) this.converter; + payload = smartConverter.fromMessage(message, targetClass, parameter); + } + else { + payload = this.converter.fromMessage(message, targetClass); + } if (payload == null) { throw new MessageConversionException(message, "No converter found to convert to " + targetClass + ", message=" + message); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractMethodMessageHandler.java index 1aaa0da8111..583ac1b85e7 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractMethodMessageHandler.java @@ -470,6 +470,11 @@ public abstract class AbstractMethodMessageHandler */ protected abstract T getMatchingMapping(T mapping, Message message); + + protected void handleNoMatch(Set ts, String lookupDestination, Message message) { + logger.debug("No matching methods."); + } + /** * Return a comparator for sorting matching mappings. * The returned comparator should sort 'better' matches higher. @@ -535,8 +540,6 @@ public abstract class AbstractMethodMessageHandler } } - protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class beanType); - /** * Find an {@code @MessageExceptionHandler} method for the given exception. * The default implementation searches methods in the class hierarchy of the @@ -575,9 +578,8 @@ public abstract class AbstractMethodMessageHandler return null; } - protected void handleNoMatch(Set ts, String lookupDestination, Message message) { - logger.debug("No matching methods."); - } + protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor( + Class beanType); @Override public String toString() { @@ -622,6 +624,7 @@ public abstract class AbstractMethodMessageHandler } } + private class ReturnValueListenableFutureCallback implements ListenableFutureCallback { private final InvocableHandlerMethod handlerMethod; diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java index 6b886aa517a..706047bb7f0 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -336,23 +336,23 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan } // Annotation-based return value types - SendToMethodReturnValueHandler sth = + SendToMethodReturnValueHandler sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, true); - sth.setHeaderInitializer(this.headerInitializer); - handlers.add(sth); + sendToHandler.setHeaderInitializer(this.headerInitializer); + handlers.add(sendToHandler); - SubscriptionMethodReturnValueHandler sh = + SubscriptionMethodReturnValueHandler subscriptionHandler = new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate); - sh.setHeaderInitializer(this.headerInitializer); - handlers.add(sh); + subscriptionHandler.setHeaderInitializer(this.headerInitializer); + handlers.add(subscriptionHandler); // custom return value types handlers.addAll(getCustomReturnValueHandlers()); // catch-all - sth = new SendToMethodReturnValueHandler(this.brokerTemplate, false); - sth.setHeaderInitializer(this.headerInitializer); - handlers.add(sth); + sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false); + sendToHandler.setHeaderInitializer(this.headerInitializer); + handlers.add(sendToHandler); return handlers; } 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 c762a130add..05877c6f3ba 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -34,15 +34,22 @@ import org.springframework.messaging.support.MessageHeaderInitializer; import org.springframework.util.Assert; /** - * A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription. - * It is supported on methods annotated with - * {@link org.springframework.messaging.simp.annotation.SubscribeMapping} - * unless they're also annotated with {@link SendTo} or {@link SendToUser} in - * which case a message is sent to the broker instead. + * {@code HandlerMethodReturnValueHandler} for replying directly to a + * subscription. It is supported on methods annotated with + * {@link org.springframework.messaging.simp.annotation.SubscribeMapping + * SubscribeMapping} such that the return value is treated as a response to be + * sent directly back on the session. This allows a client to implement + * a request-response pattern and use it for example to obtain some data upon + * initialization. * - *

The value returned from the method is converted, and turned to a {@link Message} - * and then enriched with the sessionId, subscriptionId, and destination of the - * input message. The message is then sent directly back to the connected client. + *

The value returned from the method is converted and turned into a + * {@link Message} that is then enriched with the sessionId, subscriptionId, and + * destination of the input message. + * + *

Note: this default behavior for interpreting the return + * value from an {@code @SubscribeMapping} method can be overridden through use + * of the {@link SendTo} or {@link SendToUser} annotations in which case a + * message is prepared and sent to the broker instead. * * @author Rossen Stoyanchev * @author Sebastien Deleuze @@ -60,12 +67,12 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn /** * Construct a new SubscriptionMethodReturnValueHandler. - * @param messagingTemplate a messaging template to send messages to, + * @param template a messaging template to send messages to, * most likely the "clientOutboundChannel" (must not be {@code null}) */ - public SubscriptionMethodReturnValueHandler(MessageSendingOperations messagingTemplate) { - Assert.notNull(messagingTemplate, "messagingTemplate must not be null"); - this.messagingTemplate = messagingTemplate; + public SubscriptionMethodReturnValueHandler(MessageSendingOperations template) { + Assert.notNull(template, "messagingTemplate must not be null"); + this.messagingTemplate = template; } @@ -94,7 +101,9 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn } @Override - public void handleReturnValue(Object returnValue, MethodParameter returnType, Message message) throws Exception { + public void handleReturnValue(Object returnValue, MethodParameter returnType, Message message) + throws Exception { + if (returnValue == null) { return; } @@ -105,27 +114,27 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn String subscriptionId = SimpMessageHeaderAccessor.getSubscriptionId(headers); if (subscriptionId == null) { - throw new IllegalStateException( - "No subscriptionId in " + message + " returned by: " + returnType.getMethod()); + throw new IllegalStateException("No subscriptionId in " + message + + " returned by: " + returnType.getMethod()); } if (logger.isDebugEnabled()) { logger.debug("Reply to @SubscribeMapping: " + returnValue); } - this.messagingTemplate.convertAndSend( - destination, returnValue, createHeaders(sessionId, subscriptionId, returnType)); + MessageHeaders headersToSend = createHeaders(sessionId, subscriptionId, returnType); + this.messagingTemplate.convertAndSend(destination, returnValue, headersToSend); } private MessageHeaders createHeaders(String sessionId, String subscriptionId, MethodParameter returnType) { - SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); + SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); if (getHeaderInitializer() != null) { - getHeaderInitializer().initHeaders(headerAccessor); + getHeaderInitializer().initHeaders(accessor); } - headerAccessor.setSessionId(sessionId); - headerAccessor.setSubscriptionId(subscriptionId); - headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType); - headerAccessor.setLeaveMutable(true); - return headerAccessor.getMessageHeaders(); + accessor.setSessionId(sessionId); + accessor.setSubscriptionId(subscriptionId); + accessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType); + accessor.setLeaveMutable(true); + return accessor.getMessageHeaders(); } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java index f4b7e2baf46..34541e4b851 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -60,7 +60,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload("test").build(); MethodParameter parameter = new MethodParameter(this.method, 0); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @@ -69,7 +69,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload(123).build(); MethodParameter parameter = new MethodParameter(this.method, 1); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @@ -78,7 +78,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload(123).build(); MethodParameter parameter = new MethodParameter(this.method, 2); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @@ -87,7 +87,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload("test").build(); MethodParameter parameter = new MethodParameter(this.method, 1); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(Integer.class.getName()); thrown.expectMessage(String.class.getName()); @@ -99,7 +99,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload(123).build(); MethodParameter parameter = new MethodParameter(this.method, 3); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @@ -108,7 +108,7 @@ public class MessageMethodArgumentResolverTests { Message message = MessageBuilder.withPayload(Locale.getDefault()).build(); MethodParameter parameter = new MethodParameter(this.method, 3); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(Number.class.getName()); thrown.expectMessage(Locale.class.getName()); @@ -120,7 +120,7 @@ public class MessageMethodArgumentResolverTests { ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); MethodParameter parameter = new MethodParameter(this.method, 4); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @@ -129,16 +129,17 @@ public class MessageMethodArgumentResolverTests { ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); MethodParameter parameter = new MethodParameter(this.method, 0); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolveWrongMessageType() throws Exception { - Message message = new GenericMessage(new UnsupportedOperationException()); + UnsupportedOperationException ex = new UnsupportedOperationException(); + Message message = new GenericMessage(ex); MethodParameter parameter = new MethodParameter(this.method, 4); - assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertTrue(this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(ErrorMessage.class.getName()); thrown.expectMessage(GenericMessage.class.getName());