001/*
002 * Copyright 2002-2019 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.handler.annotation.reactive;
018
019import java.lang.reflect.AnnotatedElement;
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.function.Predicate;
030
031import reactor.core.publisher.Mono;
032
033import org.springframework.beans.factory.config.ConfigurableBeanFactory;
034import org.springframework.context.ApplicationContext;
035import org.springframework.context.ConfigurableApplicationContext;
036import org.springframework.context.EmbeddedValueResolverAware;
037import org.springframework.core.KotlinDetector;
038import org.springframework.core.annotation.AnnotatedElementUtils;
039import org.springframework.core.codec.Decoder;
040import org.springframework.core.convert.ConversionService;
041import org.springframework.format.support.DefaultFormattingConversionService;
042import org.springframework.lang.Nullable;
043import org.springframework.messaging.Message;
044import org.springframework.messaging.handler.CompositeMessageCondition;
045import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
046import org.springframework.messaging.handler.HandlerMethod;
047import org.springframework.messaging.handler.annotation.MessageMapping;
048import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
049import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver;
050import org.springframework.messaging.handler.invocation.reactive.AbstractEncoderMethodReturnValueHandler;
051import org.springframework.messaging.handler.invocation.reactive.AbstractMethodMessageHandler;
052import org.springframework.messaging.handler.invocation.reactive.HandlerMethodArgumentResolver;
053import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler;
054import org.springframework.messaging.support.MessageHeaderAccessor;
055import org.springframework.stereotype.Controller;
056import org.springframework.util.AntPathMatcher;
057import org.springframework.util.Assert;
058import org.springframework.util.CollectionUtils;
059import org.springframework.util.RouteMatcher;
060import org.springframework.util.SimpleRouteMatcher;
061import org.springframework.util.StringValueResolver;
062import org.springframework.validation.Validator;
063
064/**
065 * Extension of {@link AbstractMethodMessageHandler} for reactive, non-blocking
066 * handling of messages via {@link MessageMapping @MessageMapping} methods.
067 * By default such methods are detected in {@code @Controller} Spring beans but
068 * that can be changed via {@link #setHandlerPredicate(Predicate)}.
069 *
070 * <p>Payloads for incoming messages are decoded through the configured
071 * {@link #setDecoders(List)} decoders, with the help of
072 * {@link PayloadMethodArgumentResolver}.
073 *
074 * <p>There is no default handling for return values but
075 * {@link #setReturnValueHandlerConfigurer} can be used to configure custom
076 * return value handlers. Sub-classes may also override
077 * {@link #initReturnValueHandlers()} to set up default return value handlers.
078 *
079 * @author Rossen Stoyanchev
080 * @since 5.2
081 * @see AbstractEncoderMethodReturnValueHandler
082 */
083public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<CompositeMessageCondition>
084                implements EmbeddedValueResolverAware {
085
086        private final List<Decoder<?>> decoders = new ArrayList<>();
087
088        @Nullable
089        private Validator validator;
090
091        @Nullable
092        private RouteMatcher routeMatcher;
093
094        private ConversionService conversionService = new DefaultFormattingConversionService();
095
096        @Nullable
097        private StringValueResolver valueResolver;
098
099
100        public MessageMappingMessageHandler() {
101                setHandlerPredicate(type -> AnnotatedElementUtils.hasAnnotation(type, Controller.class));
102        }
103
104
105        /**
106         * Configure the decoders to use for incoming payloads.
107         */
108        public void setDecoders(List<? extends Decoder<?>> decoders) {
109                this.decoders.clear();
110                this.decoders.addAll(decoders);
111        }
112
113        /**
114         * Return the configured decoders.
115         */
116        public List<? extends Decoder<?>> getDecoders() {
117                return this.decoders;
118        }
119
120        /**
121         * Set the Validator instance used for validating {@code @Payload} arguments.
122         * @see org.springframework.validation.annotation.Validated
123         * @see PayloadMethodArgumentResolver
124         */
125        public void setValidator(@Nullable Validator validator) {
126                this.validator = validator;
127        }
128
129        /**
130         * Return the configured Validator instance.
131         */
132        @Nullable
133        public Validator getValidator() {
134                return this.validator;
135        }
136
137        /**
138         * Set the {@code RouteMatcher} to use for mapping messages to handlers
139         * based on the route patterns they're configured with.
140         * <p>By default, {@link SimpleRouteMatcher} is used, backed by
141         * {@link AntPathMatcher} with "." as separator. For greater
142         * efficiency consider using the {@code PathPatternRouteMatcher} from
143         * {@code spring-web} instead.
144         */
145        public void setRouteMatcher(@Nullable RouteMatcher routeMatcher) {
146                this.routeMatcher = routeMatcher;
147        }
148
149        /**
150         * Return the {@code RouteMatcher} used to map messages to handlers.
151         * May be {@code null} before the component is initialized.
152         */
153        @Nullable
154        public RouteMatcher getRouteMatcher() {
155                return this.routeMatcher;
156        }
157
158        /**
159         * Obtain the {@code RouteMatcher} for actual use.
160         * @return the RouteMatcher (never {@code null})
161         * @throws IllegalStateException in case of no RouteMatcher set
162         * @since 5.0
163         */
164        protected RouteMatcher obtainRouteMatcher() {
165                RouteMatcher routeMatcher = getRouteMatcher();
166                Assert.state(routeMatcher != null, "No RouteMatcher set");
167                return routeMatcher;
168        }
169
170        /**
171         * Configure a {@link ConversionService} to use for type conversion of
172         * String based values, e.g. in destination variables or headers.
173         * <p>By default {@link DefaultFormattingConversionService} is used.
174         * @param conversionService the conversion service to use
175         */
176        public void setConversionService(ConversionService conversionService) {
177                this.conversionService = conversionService;
178        }
179
180        /**
181         * Return the configured ConversionService.
182         */
183        public ConversionService getConversionService() {
184                return this.conversionService;
185        }
186
187        @Override
188        public void setEmbeddedValueResolver(StringValueResolver resolver) {
189                this.valueResolver = resolver;
190        }
191
192
193        @Override
194        public void afterPropertiesSet() {
195
196                // Initialize RouteMatcher before parent initializes handler mappings
197                if (this.routeMatcher == null) {
198                        AntPathMatcher pathMatcher = new AntPathMatcher();
199                        pathMatcher.setPathSeparator(".");
200                        this.routeMatcher = new SimpleRouteMatcher(pathMatcher);
201                }
202
203                super.afterPropertiesSet();
204        }
205
206        @Override
207        protected List<? extends HandlerMethodArgumentResolver> initArgumentResolvers() {
208                List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
209
210                ApplicationContext context = getApplicationContext();
211                ConfigurableBeanFactory beanFactory = (context instanceof ConfigurableApplicationContext ?
212                                ((ConfigurableApplicationContext) context).getBeanFactory() : null);
213
214                // Annotation-based resolvers
215                resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory));
216                resolvers.add(new HeadersMethodArgumentResolver());
217                resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService));
218
219                // Type-based...
220                if (KotlinDetector.isKotlinPresent()) {
221                        resolvers.add(new ContinuationHandlerMethodArgumentResolver());
222                }
223
224                // Custom resolvers
225                resolvers.addAll(getArgumentResolverConfigurer().getCustomResolvers());
226
227                // Catch-all
228                resolvers.add(new PayloadMethodArgumentResolver(
229                                getDecoders(), this.validator, getReactiveAdapterRegistry(), true));
230
231                return resolvers;
232        }
233
234        @Override
235        protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() {
236                return Collections.emptyList();
237        }
238
239
240        @Override
241        protected CompositeMessageCondition getMappingForMethod(Method method, Class<?> handlerType) {
242                CompositeMessageCondition methodCondition = getCondition(method);
243                if (methodCondition != null) {
244                        CompositeMessageCondition typeCondition = getCondition(handlerType);
245                        if (typeCondition != null) {
246                                return typeCondition.combine(methodCondition);
247                        }
248                }
249                return methodCondition;
250        }
251
252        /**
253         * Determine the mapping condition for the given annotated element.
254         * @param element the element to check
255         * @return the condition, or {@code null}
256         */
257        @Nullable
258        protected CompositeMessageCondition getCondition(AnnotatedElement element) {
259                MessageMapping ann = AnnotatedElementUtils.findMergedAnnotation(element, MessageMapping.class);
260                if (ann == null || ann.value().length == 0) {
261                        return null;
262                }
263                String[] patterns = processDestinations(ann.value());
264                return new CompositeMessageCondition(
265                                new DestinationPatternsMessageCondition(patterns, obtainRouteMatcher()));
266        }
267
268        /**
269         * Resolve placeholders in the given destinations.
270         * @param destinations the destinations
271         * @return new array with the processed destinations or the same array
272         */
273        protected String[] processDestinations(String[] destinations) {
274                if (this.valueResolver != null) {
275                        destinations = Arrays.stream(destinations)
276                                        .map(s -> this.valueResolver.resolveStringValue(s))
277                                        .toArray(String[]::new);
278                }
279                return destinations;
280        }
281
282        @Override
283        protected Set<String> getDirectLookupMappings(CompositeMessageCondition mapping) {
284                Set<String> result = new LinkedHashSet<>();
285                for (String pattern : mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns()) {
286                        if (!obtainRouteMatcher().isPattern(pattern)) {
287                                result.add(pattern);
288                        }
289                }
290                return result;
291        }
292
293        @Override
294        protected RouteMatcher.Route getDestination(Message<?> message) {
295                return (RouteMatcher.Route) message.getHeaders()
296                                .get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER);
297        }
298
299        @Override
300        protected CompositeMessageCondition getMatchingMapping(CompositeMessageCondition mapping, Message<?> message) {
301                return mapping.getMatchingCondition(message);
302        }
303
304        @Override
305        protected Comparator<CompositeMessageCondition> getMappingComparator(Message<?> message) {
306                return (info1, info2) -> info1.compareTo(info2, message);
307        }
308
309        @Override
310        protected AbstractExceptionHandlerMethodResolver createExceptionMethodResolverFor(Class<?> beanType) {
311                return new AnnotationExceptionHandlerMethodResolver(beanType);
312        }
313
314        @Override
315        protected Mono<Void> handleMatch(
316                        CompositeMessageCondition mapping, HandlerMethod handlerMethod, Message<?> message) {
317
318                Set<String> patterns = mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns();
319                if (!CollectionUtils.isEmpty(patterns)) {
320                        String pattern = patterns.iterator().next();
321                        RouteMatcher.Route destination = getDestination(message);
322                        Assert.state(destination != null, "Missing destination header");
323                        Map<String, String> vars = obtainRouteMatcher().matchAndExtract(pattern, destination);
324                        if (!CollectionUtils.isEmpty(vars)) {
325                                MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
326                                Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required");
327                                mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars);
328                        }
329                }
330                return super.handleMatch(mapping, handlerMethod, message);
331        }
332
333}