001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.messaging.simp.annotation.support;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Comparator;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.springframework.beans.factory.config.ConfigurableBeanFactory;
029import org.springframework.context.ConfigurableApplicationContext;
030import org.springframework.context.EmbeddedValueResolverAware;
031import org.springframework.context.SmartLifecycle;
032import org.springframework.core.annotation.AnnotatedElementUtils;
033import org.springframework.core.convert.ConversionService;
034import org.springframework.format.support.DefaultFormattingConversionService;
035import org.springframework.messaging.Message;
036import org.springframework.messaging.MessageChannel;
037import org.springframework.messaging.SubscribableChannel;
038import org.springframework.messaging.converter.ByteArrayMessageConverter;
039import org.springframework.messaging.converter.CompositeMessageConverter;
040import org.springframework.messaging.converter.MessageConverter;
041import org.springframework.messaging.converter.StringMessageConverter;
042import org.springframework.messaging.core.AbstractMessageSendingTemplate;
043import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
044import org.springframework.messaging.handler.HandlerMethod;
045import org.springframework.messaging.handler.annotation.MessageMapping;
046import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
047import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
048import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
049import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
050import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
051import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
052import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver;
053import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
054import org.springframework.messaging.handler.invocation.CompletableFutureReturnValueHandler;
055import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
056import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
057import org.springframework.messaging.handler.invocation.ListenableFutureReturnValueHandler;
058import org.springframework.messaging.simp.SimpAttributesContextHolder;
059import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
060import org.springframework.messaging.simp.SimpMessageMappingInfo;
061import org.springframework.messaging.simp.SimpMessageSendingOperations;
062import org.springframework.messaging.simp.SimpMessageTypeMessageCondition;
063import org.springframework.messaging.simp.SimpMessagingTemplate;
064import org.springframework.messaging.simp.annotation.SubscribeMapping;
065import org.springframework.messaging.support.MessageHeaderAccessor;
066import org.springframework.messaging.support.MessageHeaderInitializer;
067import org.springframework.stereotype.Controller;
068import org.springframework.util.AntPathMatcher;
069import org.springframework.util.Assert;
070import org.springframework.util.ClassUtils;
071import org.springframework.util.CollectionUtils;
072import org.springframework.util.PathMatcher;
073import org.springframework.util.StringValueResolver;
074import org.springframework.validation.Validator;
075
076/**
077 * A handler for messages delegating to {@link MessageMapping @MessageMapping}
078 * and {@link SubscribeMapping @SubscribeMapping} annotated methods.
079 *
080 * <p>Supports Ant-style path patterns with template variables.
081 *
082 * @author Rossen Stoyanchev
083 * @author Brian Clozel
084 * @author Juergen Hoeller
085 * @since 4.0
086 */
087public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler<SimpMessageMappingInfo>
088                implements EmbeddedValueResolverAware, SmartLifecycle {
089
090        private static final boolean completableFuturePresent = ClassUtils.isPresent(
091                        "java.util.concurrent.CompletableFuture", SimpAnnotationMethodMessageHandler.class.getClassLoader());
092
093
094        private final SubscribableChannel clientInboundChannel;
095
096        private final SimpMessageSendingOperations clientMessagingTemplate;
097
098        private final SimpMessageSendingOperations brokerTemplate;
099
100        private MessageConverter messageConverter;
101
102        private ConversionService conversionService = new DefaultFormattingConversionService();
103
104        private PathMatcher pathMatcher = new AntPathMatcher();
105
106        private boolean slashPathSeparator = true;
107
108        private Validator validator;
109
110        private StringValueResolver valueResolver;
111
112        private MessageHeaderInitializer headerInitializer;
113
114        private volatile boolean running = false;
115
116        private final Object lifecycleMonitor = new Object();
117
118
119        /**
120         * Create an instance of SimpAnnotationMethodMessageHandler with the given
121         * message channels and broker messaging template.
122         * @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
123         * @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients)
124         * @param brokerTemplate a messaging template to send application messages to the broker
125         */
126        public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
127                        MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
128
129                Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
130                Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
131                Assert.notNull(brokerTemplate, "brokerTemplate must not be null");
132
133                this.clientInboundChannel = clientInboundChannel;
134                this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
135                this.brokerTemplate = brokerTemplate;
136
137                Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
138                converters.add(new StringMessageConverter());
139                converters.add(new ByteArrayMessageConverter());
140                this.messageConverter = new CompositeMessageConverter(converters);
141        }
142
143
144        /**
145         * {@inheritDoc}
146         * <p>Destination prefixes are expected to be slash-separated Strings and
147         * therefore a slash is automatically appended where missing to ensure a
148         * proper prefix-based match (i.e. matching complete segments).
149         * <p>Note however that the remaining portion of a destination after the
150         * prefix may use a different separator (e.g. commonly "." in messaging)
151         * depending on the configured {@code PathMatcher}.
152         */
153        @Override
154        public void setDestinationPrefixes(Collection<String> prefixes) {
155                super.setDestinationPrefixes(appendSlashes(prefixes));
156        }
157
158        private static Collection<String> appendSlashes(Collection<String> prefixes) {
159                if (CollectionUtils.isEmpty(prefixes)) {
160                        return prefixes;
161                }
162                Collection<String> result = new ArrayList<String>(prefixes.size());
163                for (String prefix : prefixes) {
164                        if (!prefix.endsWith("/")) {
165                                prefix = prefix + "/";
166                        }
167                        result.add(prefix);
168                }
169                return result;
170        }
171
172        /**
173         * Configure a {@link MessageConverter} to use to convert the payload of a message from
174         * its serialized form with a specific MIME type to an Object matching the target method
175         * parameter. The converter is also used when sending a message to the message broker.
176         * @see CompositeMessageConverter
177         */
178        public void setMessageConverter(MessageConverter converter) {
179                this.messageConverter = converter;
180                if (converter != null) {
181                        ((AbstractMessageSendingTemplate<?>) this.clientMessagingTemplate).setMessageConverter(converter);
182                }
183        }
184
185        /**
186         * Return the configured {@link MessageConverter}.
187         */
188        public MessageConverter getMessageConverter() {
189                return this.messageConverter;
190        }
191
192        /**
193         * Configure a {@link ConversionService} to use when resolving method arguments,
194         * for example message header values.
195         * <p>By default, {@link DefaultFormattingConversionService} is used.
196         */
197        public void setConversionService(ConversionService conversionService) {
198                this.conversionService = conversionService;
199        }
200
201        /**
202         * Return the configured {@link ConversionService}.
203         */
204        public ConversionService getConversionService() {
205                return this.conversionService;
206        }
207
208        /**
209         * Set the PathMatcher implementation to use for matching destinations
210         * against configured destination patterns.
211         * <p>By default, {@link AntPathMatcher} is used.
212         */
213        public void setPathMatcher(PathMatcher pathMatcher) {
214                Assert.notNull(pathMatcher, "PathMatcher must not be null");
215                this.pathMatcher = pathMatcher;
216                this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a");
217        }
218
219        /**
220         * Return the PathMatcher implementation to use for matching destinations.
221         */
222        public PathMatcher getPathMatcher() {
223                return this.pathMatcher;
224        }
225
226        /**
227         * Return the configured Validator instance.
228         */
229        public Validator getValidator() {
230                return this.validator;
231        }
232
233        /**
234         * Set the Validator instance used for validating {@code @Payload} arguments.
235         * @see org.springframework.validation.annotation.Validated
236         * @see PayloadArgumentResolver
237         */
238        public void setValidator(Validator validator) {
239                this.validator = validator;
240        }
241
242        @Override
243        public void setEmbeddedValueResolver(StringValueResolver resolver) {
244                this.valueResolver = resolver;
245        }
246
247        /**
248         * Configure a {@link MessageHeaderInitializer} to pass on to
249         * {@link org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler}s
250         * that send messages from controller return values.
251         * <p>By default, this property is not set.
252         */
253        public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
254                this.headerInitializer = headerInitializer;
255        }
256
257        /**
258         * Return the configured header initializer.
259         */
260        public MessageHeaderInitializer getHeaderInitializer() {
261                return this.headerInitializer;
262        }
263
264
265        @Override
266        public boolean isAutoStartup() {
267                return true;
268        }
269
270        @Override
271        public int getPhase() {
272                return Integer.MAX_VALUE;
273        }
274
275        @Override
276        public final void start() {
277                synchronized (this.lifecycleMonitor) {
278                        this.clientInboundChannel.subscribe(this);
279                        this.running = true;
280                }
281        }
282
283        @Override
284        public final void stop() {
285                synchronized (this.lifecycleMonitor) {
286                        this.running = false;
287                        this.clientInboundChannel.unsubscribe(this);
288                }
289        }
290
291        @Override
292        public final void stop(Runnable callback) {
293                synchronized (this.lifecycleMonitor) {
294                        stop();
295                        callback.run();
296                }
297        }
298
299        @Override
300        public final boolean isRunning() {
301                return this.running;
302        }
303
304
305        protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
306                ConfigurableBeanFactory beanFactory = (getApplicationContext() instanceof ConfigurableApplicationContext ?
307                                ((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory() : null);
308
309                List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
310
311                // Annotation-based argument resolution
312                resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory));
313                resolvers.add(new HeadersMethodArgumentResolver());
314                resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService));
315
316                // Type-based argument resolution
317                resolvers.add(new PrincipalMethodArgumentResolver());
318                resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));
319
320                resolvers.addAll(getCustomArgumentResolvers());
321                resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));
322
323                return resolvers;
324        }
325
326        @Override
327        protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() {
328                List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
329
330                // Single-purpose return value types
331                handlers.add(new ListenableFutureReturnValueHandler());
332                if (completableFuturePresent) {
333                        handlers.add(new CompletableFutureReturnValueHandler());
334                }
335
336                // Annotation-based return value types
337                SendToMethodReturnValueHandler sendToHandler =
338                                new SendToMethodReturnValueHandler(this.brokerTemplate, true);
339                sendToHandler.setHeaderInitializer(this.headerInitializer);
340                handlers.add(sendToHandler);
341
342                SubscriptionMethodReturnValueHandler subscriptionHandler =
343                                new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate);
344                subscriptionHandler.setHeaderInitializer(this.headerInitializer);
345                handlers.add(subscriptionHandler);
346
347                // custom return value types
348                handlers.addAll(getCustomReturnValueHandlers());
349
350                // catch-all
351                sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false);
352                sendToHandler.setHeaderInitializer(this.headerInitializer);
353                handlers.add(sendToHandler);
354
355                return handlers;
356        }
357
358
359        @Override
360        protected boolean isHandler(Class<?> beanType) {
361                return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
362        }
363
364        @Override
365        protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
366                MessageMapping messageAnn = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class);
367                if (messageAnn != null) {
368                        MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class);
369                        // Only actually register it if there are destinations specified;
370                        // otherwise @MessageMapping is just being used as a (meta-annotation) marker.
371                        if (messageAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) {
372                                SimpMessageMappingInfo result = createMessageMappingCondition(messageAnn.value());
373                                if (typeAnn != null) {
374                                        result = createMessageMappingCondition(typeAnn.value()).combine(result);
375                                }
376                                return result;
377                        }
378                }
379
380                SubscribeMapping subscribeAnn = AnnotatedElementUtils.findMergedAnnotation(method, SubscribeMapping.class);
381                if (subscribeAnn != null) {
382                        MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class);
383                        // Only actually register it if there are destinations specified;
384                        // otherwise @SubscribeMapping is just being used as a (meta-annotation) marker.
385                        if (subscribeAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) {
386                                SimpMessageMappingInfo result = createSubscribeMappingCondition(subscribeAnn.value());
387                                if (typeAnn != null) {
388                                        result = createMessageMappingCondition(typeAnn.value()).combine(result);
389                                }
390                                return result;
391                        }
392                }
393
394                return null;
395        }
396
397        private SimpMessageMappingInfo createMessageMappingCondition(String[] destinations) {
398                String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations);
399                return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
400                                new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher));
401        }
402
403        private SimpMessageMappingInfo createSubscribeMappingCondition(String[] destinations) {
404                String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations);
405                return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
406                                new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher));
407        }
408
409        /**
410         * Resolve placeholder values in the given array of destinations.
411         * @return a new array with updated destinations
412         * @since 4.2
413         */
414        protected String[] resolveEmbeddedValuesInDestinations(String[] destinations) {
415                if (this.valueResolver == null) {
416                        return destinations;
417                }
418                String[] result = new String[destinations.length];
419                for (int i = 0; i < destinations.length; i++) {
420                        result[i] = this.valueResolver.resolveStringValue(destinations[i]);
421                }
422                return result;
423        }
424
425        @Override
426        protected Set<String> getDirectLookupDestinations(SimpMessageMappingInfo mapping) {
427                Set<String> result = new LinkedHashSet<String>();
428                for (String pattern : mapping.getDestinationConditions().getPatterns()) {
429                        if (!this.pathMatcher.isPattern(pattern)) {
430                                result.add(pattern);
431                        }
432                }
433                return result;
434        }
435
436        @Override
437        protected String getDestination(Message<?> message) {
438                return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
439        }
440
441        @Override
442        protected String getLookupDestination(String destination) {
443                if (destination == null) {
444                        return null;
445                }
446                if (CollectionUtils.isEmpty(getDestinationPrefixes())) {
447                        return destination;
448                }
449                for (String prefix : getDestinationPrefixes()) {
450                        if (destination.startsWith(prefix)) {
451                                if (this.slashPathSeparator) {
452                                        return destination.substring(prefix.length() - 1);
453                                }
454                                else {
455                                        return destination.substring(prefix.length());
456                                }
457                        }
458                }
459                return null;
460        }
461
462        @Override
463        protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
464                return mapping.getMatchingCondition(message);
465
466        }
467
468        @Override
469        protected Comparator<SimpMessageMappingInfo> getMappingComparator(final Message<?> message) {
470                return new Comparator<SimpMessageMappingInfo>() {
471                        @Override
472                        public int compare(SimpMessageMappingInfo info1, SimpMessageMappingInfo info2) {
473                                return info1.compareTo(info2, message);
474                        }
475                };
476        }
477
478        @Override
479        protected void handleMatch(SimpMessageMappingInfo mapping, HandlerMethod handlerMethod,
480                        String lookupDestination, Message<?> message) {
481
482                Set<String> patterns = mapping.getDestinationConditions().getPatterns();
483                if (!CollectionUtils.isEmpty(patterns)) {
484                        String pattern = patterns.iterator().next();
485                        Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(pattern, lookupDestination);
486                        if (!CollectionUtils.isEmpty(vars)) {
487                                MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
488                                Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required");
489                                mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars);
490                        }
491                }
492
493                try {
494                        SimpAttributesContextHolder.setAttributesFromMessage(message);
495                        super.handleMatch(mapping, handlerMethod, lookupDestination, message);
496                }
497                finally {
498                        SimpAttributesContextHolder.resetAttributes();
499                }
500        }
501
502        @Override
503        protected AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType) {
504                return new AnnotationExceptionHandlerMethodResolver(beanType);
505        }
506
507}