summaryrefslogtreecommitdiff
path: root/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
committerEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
commitda46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch)
tree52b707fbbccd5b6100088913f32c1cbd00568790 /spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support
parentc03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff)
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support')
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/MissingSessionUserException.java33
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/PrincipalMethodArgumentResolver.java48
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java188
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java366
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java108
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/package-info.java5
6 files changed, 748 insertions, 0 deletions
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/MissingSessionUserException.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/MissingSessionUserException.java
new file mode 100644
index 00000000..c8cb88dd
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/MissingSessionUserException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.messaging.simp.annotation.support;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessagingException;
+
+/**
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+@SuppressWarnings("serial")
+public class MissingSessionUserException extends MessagingException {
+
+ public MissingSessionUserException(Message<?> message) {
+ super(message, "No \"user\" header in message");
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/PrincipalMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/PrincipalMethodArgumentResolver.java
new file mode 100644
index 00000000..dedbaed0
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/PrincipalMethodArgumentResolver.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.messaging.simp.annotation.support;
+
+import java.security.Principal;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+
+/**
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ Class<?> paramType = parameter.getParameterType();
+ return Principal.class.isAssignableFrom(paramType);
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
+ Principal user = headers.getUser();
+ if (user == null) {
+ throw new MissingSessionUserException(message);
+ }
+ return user;
+ }
+
+}
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
new file mode 100644
index 00000000..957505cb
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.messaging.simp.annotation.support;
+
+import java.lang.annotation.Annotation;
+import java.security.Principal;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.core.MessagePostProcessor;
+import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.simp.SimpMessageSendingOperations;
+import org.springframework.messaging.simp.annotation.SendToUser;
+import org.springframework.messaging.simp.user.DestinationUserNameProvider;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * A {@link HandlerMethodReturnValueHandler} for sending to destinations specified in a
+ * {@link SendTo} or {@link SendToUser} method-level annotations.
+ *
+ * <p>The value returned from the method is converted, and turned to a {@link Message} and
+ * sent through the provided {@link MessageChannel}. The
+ * message is then enriched with the sessionId of the input message as well as the
+ * destination from the annotation(s). If multiple destinations are specified, a copy of
+ * the message is sent to each destination.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ private final SimpMessageSendingOperations messagingTemplate;
+
+ private final boolean annotationRequired;
+
+ private String defaultDestinationPrefix = "/topic";
+
+ private String defaultUserDestinationPrefix = "/queue";
+
+
+ public SendToMethodReturnValueHandler(SimpMessageSendingOperations messagingTemplate, boolean annotationRequired) {
+ Assert.notNull(messagingTemplate, "messagingTemplate must not be null");
+ this.messagingTemplate = messagingTemplate;
+ this.annotationRequired = annotationRequired;
+ }
+
+
+ /**
+ * Configure a default prefix to add to message destinations in cases where a method
+ * is not annotated with {@link SendTo @SendTo} or does not specify any destinations
+ * through the annotation's value attribute.
+ * <p>By default, the prefix is set to "/topic".
+ */
+ public void setDefaultDestinationPrefix(String defaultDestinationPrefix) {
+ this.defaultDestinationPrefix = defaultDestinationPrefix;
+ }
+
+ /**
+ * Return the configured default destination prefix.
+ * @see #setDefaultDestinationPrefix(String)
+ */
+ public String getDefaultDestinationPrefix() {
+ return this.defaultDestinationPrefix;
+ }
+
+ /**
+ * Configure a default prefix to add to message destinations in cases where a
+ * method is annotated with {@link SendToUser @SendToUser} but does not specify
+ * any destinations through the annotation's value attribute.
+ * <p>By default, the prefix is set to "/queue".
+ */
+ public void setDefaultUserDestinationPrefix(String prefix) {
+ this.defaultUserDestinationPrefix = prefix;
+ }
+
+ /**
+ * Return the configured default user destination prefix.
+ * @see #setDefaultUserDestinationPrefix(String)
+ */
+ public String getDefaultUserDestinationPrefix() {
+ return this.defaultUserDestinationPrefix;
+ }
+
+
+ @Override
+ public boolean supportsReturnType(MethodParameter returnType) {
+ if ((returnType.getMethodAnnotation(SendTo.class) != null) ||
+ (returnType.getMethodAnnotation(SendToUser.class) != null)) {
+ return true;
+ }
+ return (!this.annotationRequired);
+ }
+
+ @Override
+ public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> inputMessage)
+ throws Exception {
+
+ if (returnValue == null) {
+ return;
+ }
+
+ SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(inputMessage);
+
+ String sessionId = inputHeaders.getSessionId();
+ MessagePostProcessor postProcessor = new SessionHeaderPostProcessor(sessionId);
+
+ SendToUser sendToUser = returnType.getMethodAnnotation(SendToUser.class);
+ if (sendToUser != null) {
+ Principal principal = inputHeaders.getUser();
+ if (principal == null) {
+ throw new MissingSessionUserException(inputMessage);
+ }
+ String userName = principal.getName();
+ if (principal instanceof DestinationUserNameProvider) {
+ userName = ((DestinationUserNameProvider) principal).getDestinationUserName();
+ }
+ String[] destinations = getTargetDestinations(sendToUser, inputHeaders, this.defaultUserDestinationPrefix);
+ for (String destination : destinations) {
+ this.messagingTemplate.convertAndSendToUser(userName, destination, returnValue, postProcessor);
+ }
+ return;
+ }
+ else {
+ SendTo sendTo = returnType.getMethodAnnotation(SendTo.class);
+ String[] destinations = getTargetDestinations(sendTo, inputHeaders, this.defaultDestinationPrefix);
+ for (String destination : destinations) {
+ this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
+ }
+ }
+ }
+
+ protected String[] getTargetDestinations(Annotation annot, SimpMessageHeaderAccessor inputHeaders,
+ String defaultPrefix) {
+
+ if (annot != null) {
+ String[] value = (String[]) AnnotationUtils.getValue(annot);
+ if (!ObjectUtils.isEmpty(value)) {
+ return value;
+ }
+ }
+ return new String[] { defaultPrefix +
+ inputHeaders.getHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER) };
+ }
+
+
+ private final class SessionHeaderPostProcessor implements MessagePostProcessor {
+
+ private final String sessionId;
+
+ public SessionHeaderPostProcessor(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
+ @Override
+ public Message<?> postProcessMessage(Message<?> message) {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
+ headers.setSessionId(this.sessionId);
+ return MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SendToMethodReturnValueHandler [annotationRequired=" + annotationRequired + "]";
+ }
+
+}
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
new file mode 100644
index 00000000..f5206a28
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.messaging.simp.annotation.support;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.SmartLifecycle;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.format.support.DefaultFormattingConversionService;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.messaging.converter.ByteArrayMessageConverter;
+import org.springframework.messaging.converter.CompositeMessageConverter;
+import org.springframework.messaging.converter.MessageConverter;
+import org.springframework.messaging.converter.StringMessageConverter;
+import org.springframework.messaging.core.AbstractMessageSendingTemplate;
+import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
+import org.springframework.messaging.handler.HandlerMethod;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
+import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
+import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver;
+import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
+import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
+import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.simp.SimpMessageMappingInfo;
+import org.springframework.messaging.simp.SimpMessageSendingOperations;
+import org.springframework.messaging.simp.SimpMessageTypeMessageCondition;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.simp.annotation.SubscribeMapping;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.PathMatcher;
+import org.springframework.validation.Validator;
+
+/**
+ * A handler for messages delegating to {@link MessageMapping @MessageMapping}
+ * and {@link SubscribeMapping @SubscribeMapping} annotated methods.
+ *
+ * <p>Supports Ant-style path patterns with template variables.
+ *
+ * @author Rossen Stoyanchev
+ * @author Brian Clozel
+ * @since 4.0
+ */
+public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler<SimpMessageMappingInfo>
+ implements SmartLifecycle {
+
+ private final SubscribableChannel clientInboundChannel;
+
+ private final SimpMessageSendingOperations clientMessagingTemplate;
+
+ private final SimpMessageSendingOperations brokerTemplate;
+
+ private MessageConverter messageConverter;
+
+ private ConversionService conversionService = new DefaultFormattingConversionService();
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+ private Validator validator;
+
+ private final Object lifecycleMonitor = new Object();
+
+ private volatile boolean running = false;
+
+
+ /**
+ * Create an instance of SimpAnnotationMethodMessageHandler with the given
+ * message channels and broker messaging template.
+ * @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
+ * @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients)
+ * @param brokerTemplate a messaging template to send application messages to the broker
+ */
+ public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
+ MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
+
+ Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
+ Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
+ Assert.notNull(brokerTemplate, "brokerTemplate must not be null");
+
+ this.clientInboundChannel = clientInboundChannel;
+ this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
+ this.brokerTemplate = brokerTemplate;
+
+ Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
+ converters.add(new StringMessageConverter());
+ converters.add(new ByteArrayMessageConverter());
+ this.messageConverter = new CompositeMessageConverter(converters);
+ }
+
+
+ /**
+ * Configure a {@link MessageConverter} to use to convert the payload of a message from
+ * its serialized form with a specific MIME type to an Object matching the target method
+ * parameter. The converter is also used when sending a message to the message broker.
+ * @see CompositeMessageConverter
+ */
+ public void setMessageConverter(MessageConverter converter) {
+ this.messageConverter = converter;
+ if (converter != null) {
+ ((AbstractMessageSendingTemplate<?>) this.clientMessagingTemplate).setMessageConverter(converter);
+ }
+ }
+
+ /**
+ * Return the configured {@link MessageConverter}.
+ */
+ public MessageConverter getMessageConverter() {
+ return this.messageConverter;
+ }
+
+ /**
+ * Configure a {@link ConversionService} to use when resolving method arguments,
+ * for example message header values.
+ * <p>By default an instance of {@link DefaultFormattingConversionService} is used.
+ */
+ public void setConversionService(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Return the configured {@link ConversionService}.
+ */
+ public ConversionService getConversionService() {
+ return this.conversionService;
+ }
+
+ /**
+ * Set the PathMatcher implementation to use for matching destinations
+ * against configured destination patterns.
+ * <p>By default, {@link AntPathMatcher} is used.
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ Assert.notNull(pathMatcher, "PathMatcher must not be null");
+ this.pathMatcher = pathMatcher;
+ }
+
+ /**
+ * Return the PathMatcher implementation to use for matching destinations.
+ */
+ public PathMatcher getPathMatcher() {
+ return this.pathMatcher;
+ }
+
+ /**
+ * Return the configured Validator instance.
+ */
+ public Validator getValidator() {
+ return this.validator;
+ }
+
+ /**
+ * Set the Validator instance used for validating @Payload arguments
+ * @see org.springframework.validation.annotation.Validated
+ * @see PayloadArgumentResolver
+ */
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+
+ @Override
+ public boolean isAutoStartup() {
+ return true;
+ }
+
+ @Override
+ public int getPhase() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public final boolean isRunning() {
+ synchronized (this.lifecycleMonitor) {
+ return this.running;
+ }
+ }
+
+ @Override
+ public final void start() {
+ synchronized (this.lifecycleMonitor) {
+ this.clientInboundChannel.subscribe(this);
+ this.running = true;
+ }
+ }
+
+ @Override
+ public final void stop() {
+ synchronized (this.lifecycleMonitor) {
+ this.running = false;
+ this.clientInboundChannel.unsubscribe(this);
+ }
+ }
+
+ @Override
+ public final void stop(Runnable callback) {
+ synchronized (this.lifecycleMonitor) {
+ stop();
+ callback.run();
+ }
+ }
+
+
+ protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
+ ConfigurableBeanFactory beanFactory =
+ (ClassUtils.isAssignableValue(ConfigurableApplicationContext.class, getApplicationContext())) ?
+ ((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory() : null;
+
+ List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
+
+ // Annotation-based argument resolution
+ resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory));
+ resolvers.add(new HeadersMethodArgumentResolver());
+ resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService));
+
+ // Type-based argument resolution
+ resolvers.add(new PrincipalMethodArgumentResolver());
+ resolvers.add(new MessageMethodArgumentResolver());
+
+ resolvers.addAll(getCustomArgumentResolvers());
+ resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));
+
+ return resolvers;
+ }
+
+ @Override
+ protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() {
+ List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
+
+ // Annotation-based return value types
+ handlers.add(new SendToMethodReturnValueHandler(this.brokerTemplate, true));
+ handlers.add(new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate));
+
+ // custom return value types
+ handlers.addAll(getCustomReturnValueHandlers());
+
+ // catch-all
+ handlers.add(new SendToMethodReturnValueHandler(this.brokerTemplate, false));
+
+ return handlers;
+ }
+
+
+ @Override
+ protected boolean isHandler(Class<?> beanType) {
+ return (AnnotationUtils.findAnnotation(beanType, Controller.class) != null);
+ }
+
+ @Override
+ protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
+ MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
+ MessageMapping messageAnnotation = AnnotationUtils.findAnnotation(method, MessageMapping.class);
+ if (messageAnnotation != null) {
+ SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnotation);
+ if (typeAnnotation != null) {
+ result = createMessageMappingCondition(typeAnnotation).combine(result);
+ }
+ return result;
+ }
+ SubscribeMapping subscribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
+ if (subscribeAnnotation != null) {
+ SimpMessageMappingInfo result = createSubscribeCondition(subscribeAnnotation);
+ if (typeAnnotation != null) {
+ result = createMessageMappingCondition(typeAnnotation).combine(result);
+ }
+ return result;
+ }
+ return null;
+ }
+
+ private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
+ return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
+ new DestinationPatternsMessageCondition(annotation.value()));
+ }
+
+ private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
+ return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
+ new DestinationPatternsMessageCondition(annotation.value()));
+ }
+
+ @Override
+ protected Set<String> getDirectLookupDestinations(SimpMessageMappingInfo mapping) {
+ Set<String> result = new LinkedHashSet<String>();
+ for (String pattern : mapping.getDestinationConditions().getPatterns()) {
+ if (!this.pathMatcher.isPattern(pattern)) {
+ result.add(pattern);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected String getDestination(Message<?> message) {
+ return (String) message.getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER);
+ }
+
+ @Override
+ protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
+ return mapping.getMatchingCondition(message);
+
+ }
+
+ @Override
+ protected Comparator<SimpMessageMappingInfo> getMappingComparator(final Message<?> message) {
+ return new Comparator<SimpMessageMappingInfo>() {
+ @Override
+ public int compare(SimpMessageMappingInfo info1, SimpMessageMappingInfo info2) {
+ return info1.compareTo(info2, message);
+ }
+ };
+ }
+
+ @Override
+ protected void handleMatch(SimpMessageMappingInfo mapping, HandlerMethod handlerMethod,
+ String lookupDestination, Message<?> message) {
+
+ String matchedPattern = mapping.getDestinationConditions().getPatterns().iterator().next();
+ Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchedPattern, lookupDestination);
+
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
+ headers.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars);
+ message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
+
+ super.handleMatch(mapping, handlerMethod, lookupDestination, message);
+ }
+
+ @Override
+ protected AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType) {
+ return new AnnotationExceptionHandlerMethodResolver(beanType);
+ }
+
+}
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
new file mode 100644
index 00000000..9bca795f
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.messaging.simp.annotation.support;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.core.MessagePostProcessor;
+import org.springframework.messaging.core.MessageSendingOperations;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.simp.SimpMessageType;
+import org.springframework.messaging.simp.annotation.SendToUser;
+import org.springframework.messaging.simp.annotation.SubscribeMapping;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription. It
+ * supports methods annotated with {@link org.springframework.messaging.simp.annotation.SubscribeMapping} unless they're also annotated
+ * with {@link SendTo} or {@link SendToUser}.
+ *
+ * <p>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.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ private final MessageSendingOperations<String> messagingTemplate;
+
+
+ /**
+ * @param messagingTemplate a messaging template for sending messages directly
+ * to clients, e.g. in response to a subscription
+ */
+ public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
+ Assert.notNull(messagingTemplate, "messagingTemplate must not be null");
+ this.messagingTemplate = messagingTemplate;
+ }
+
+
+ @Override
+ public boolean supportsReturnType(MethodParameter returnType) {
+ return ((returnType.getMethodAnnotation(SubscribeMapping.class) != null)
+ && (returnType.getMethodAnnotation(SendTo.class) == null)
+ && (returnType.getMethodAnnotation(SendToUser.class) == null));
+ }
+
+ @Override
+ public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
+ throws Exception {
+
+ if (returnValue == null) {
+ return;
+ }
+
+ SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(message);
+ String sessionId = inputHeaders.getSessionId();
+ String subscriptionId = inputHeaders.getSubscriptionId();
+ String destination = inputHeaders.getDestination();
+
+ Assert.state(inputHeaders.getSubscriptionId() != null,
+ "No subsriptiondId in input message to method " + returnType.getMethod());
+
+ MessagePostProcessor postProcessor = new SubscriptionHeaderPostProcessor(sessionId, subscriptionId);
+ this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
+ }
+
+
+ private final class SubscriptionHeaderPostProcessor implements MessagePostProcessor {
+
+ private final String sessionId;
+
+ private final String subscriptionId;
+
+
+ public SubscriptionHeaderPostProcessor(String sessionId, String subscriptionId) {
+ this.sessionId = sessionId;
+ this.subscriptionId = subscriptionId;
+ }
+
+ @Override
+ public Message<?> postProcessMessage(Message<?> message) {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
+ headers.setSessionId(this.sessionId);
+ headers.setSubscriptionId(this.subscriptionId);
+ headers.setMessageTypeIfNotSet(SimpMessageType.MESSAGE);
+ return MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
+ }
+ }
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/package-info.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/package-info.java
new file mode 100644
index 00000000..2cddb57e
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Support classes for handling messages from simple messaging protocols
+ * (like STOMP).
+ */
+package org.springframework.messaging.simp.annotation.support;