diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
commit | da46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch) | |
tree | 52b707fbbccd5b6100088913f32c1cbd00568790 /spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support | |
parent | c03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff) |
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support')
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; |