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.invocation;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.function.Function;
031import java.util.stream.Collectors;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.beans.factory.InitializingBean;
037import org.springframework.context.ApplicationContext;
038import org.springframework.context.ApplicationContextAware;
039import org.springframework.core.MethodIntrospector;
040import org.springframework.core.MethodParameter;
041import org.springframework.lang.Nullable;
042import org.springframework.messaging.Message;
043import org.springframework.messaging.MessageHandler;
044import org.springframework.messaging.MessageHandlingException;
045import org.springframework.messaging.MessagingException;
046import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
047import org.springframework.messaging.handler.HandlerMethod;
048import org.springframework.messaging.handler.MessagingAdviceBean;
049import org.springframework.messaging.support.MessageBuilder;
050import org.springframework.messaging.support.MessageHeaderAccessor;
051import org.springframework.util.Assert;
052import org.springframework.util.ClassUtils;
053import org.springframework.util.CollectionUtils;
054import org.springframework.util.LinkedMultiValueMap;
055import org.springframework.util.MultiValueMap;
056import org.springframework.util.concurrent.ListenableFuture;
057import org.springframework.util.concurrent.ListenableFutureCallback;
058
059/**
060 * Abstract base class for HandlerMethod-based message handling. Provides most of
061 * the logic required to discover handler methods at startup, find a matching handler
062 * method at runtime for a given message and invoke it.
063 *
064 * <p>Also supports discovering and invoking exception handling methods to process
065 * exceptions raised during message handling.
066 *
067 * @author Rossen Stoyanchev
068 * @author Juergen Hoeller
069 * @since 4.0
070 * @param <T> the type of the Object that contains information mapping a
071 * {@link org.springframework.messaging.handler.HandlerMethod} to incoming messages
072 */
073public abstract class AbstractMethodMessageHandler<T>
074                implements MessageHandler, ApplicationContextAware, InitializingBean {
075
076        /**
077         * Bean name prefix for target beans behind scoped proxies. Used to exclude those
078         * targets from handler method detection, in favor of the corresponding proxies.
079         * <p>We're not checking the autowire-candidate status here, which is how the
080         * proxy target filtering problem is being handled at the autowiring level,
081         * since autowire-candidate may have been turned to {@code false} for other
082         * reasons, while still expecting the bean to be eligible for handler methods.
083         * <p>Originally defined in {@link org.springframework.aop.scope.ScopedProxyUtils}
084         * but duplicated here to avoid a hard dependency on the spring-aop module.
085         */
086        private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
087
088
089        protected final Log logger = LogFactory.getLog(getClass());
090
091        @Nullable
092        private Log handlerMethodLogger;
093
094
095        private final List<String> destinationPrefixes = new ArrayList<>();
096
097        private final List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>(4);
098
099        private final List<HandlerMethodReturnValueHandler> customReturnValueHandlers = new ArrayList<>(4);
100
101        private final HandlerMethodArgumentResolverComposite argumentResolvers =
102                        new HandlerMethodArgumentResolverComposite();
103
104        private final HandlerMethodReturnValueHandlerComposite returnValueHandlers =
105                        new HandlerMethodReturnValueHandlerComposite();
106
107        @Nullable
108        private ApplicationContext applicationContext;
109
110        private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<>(64);
111
112        private final MultiValueMap<String, T> destinationLookup = new LinkedMultiValueMap<>(64);
113
114        private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache =
115                        new ConcurrentHashMap<>(64);
116
117        private final Map<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
118                        new LinkedHashMap<>(64);
119
120
121        /**
122         * When this property is configured only messages to destinations matching
123         * one of the configured prefixes are eligible for handling. When there is a
124         * match the prefix is removed and only the remaining part of the destination
125         * is used for method-mapping purposes.
126         * <p>By default, no prefixes are configured in which case all messages are
127         * eligible for handling.
128         */
129        public void setDestinationPrefixes(@Nullable Collection<String> prefixes) {
130                this.destinationPrefixes.clear();
131                if (prefixes != null) {
132                        for (String prefix : prefixes) {
133                                prefix = prefix.trim();
134                                this.destinationPrefixes.add(prefix);
135                        }
136                }
137        }
138
139        /**
140         * Return the configured destination prefixes, if any.
141         */
142        public Collection<String> getDestinationPrefixes() {
143                return this.destinationPrefixes;
144        }
145
146        /**
147         * Sets the list of custom {@code HandlerMethodArgumentResolver}s that will be used
148         * after resolvers for supported argument type.
149         */
150        public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> customArgumentResolvers) {
151                this.customArgumentResolvers.clear();
152                if (customArgumentResolvers != null) {
153                        this.customArgumentResolvers.addAll(customArgumentResolvers);
154                }
155        }
156
157        /**
158         * Return the configured custom argument resolvers, if any.
159         */
160        public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
161                return this.customArgumentResolvers;
162        }
163
164        /**
165         * Set the list of custom {@code HandlerMethodReturnValueHandler}s that will be used
166         * after return value handlers for known types.
167         */
168        public void setCustomReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> customReturnValueHandlers) {
169                this.customReturnValueHandlers.clear();
170                if (customReturnValueHandlers != null) {
171                        this.customReturnValueHandlers.addAll(customReturnValueHandlers);
172                }
173        }
174
175        /**
176         * Return the configured custom return value handlers, if any.
177         */
178        public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
179                return this.customReturnValueHandlers;
180        }
181
182        /**
183         * Configure the complete list of supported argument types, effectively overriding
184         * the ones configured by default. This is an advanced option; for most use cases
185         * it should be sufficient to use {@link #setCustomArgumentResolvers}.
186         */
187        public void setArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
188                if (argumentResolvers == null) {
189                        this.argumentResolvers.clear();
190                        return;
191                }
192                this.argumentResolvers.addResolvers(argumentResolvers);
193        }
194
195        /**
196         * Return the complete list of argument resolvers.
197         */
198        public List<HandlerMethodArgumentResolver> getArgumentResolvers() {
199                return this.argumentResolvers.getResolvers();
200        }
201
202        /**
203         * Configure the complete list of supported return value types, effectively overriding
204         * the ones configured by default. This is an advanced option; for most use cases
205         * it should be sufficient to use {@link #setCustomReturnValueHandlers}.
206         */
207        public void setReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
208                if (returnValueHandlers == null) {
209                        this.returnValueHandlers.clear();
210                        return;
211                }
212                this.returnValueHandlers.addHandlers(returnValueHandlers);
213        }
214
215        /**
216         * Return the complete list of return value handlers.
217         */
218        public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
219                return this.returnValueHandlers.getReturnValueHandlers();
220        }
221
222        @Override
223        public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
224                this.applicationContext = applicationContext;
225        }
226
227        @Nullable
228        public ApplicationContext getApplicationContext() {
229                return this.applicationContext;
230        }
231
232
233        @Override
234        public void afterPropertiesSet() {
235                if (this.argumentResolvers.getResolvers().isEmpty()) {
236                        this.argumentResolvers.addResolvers(initArgumentResolvers());
237                }
238
239                if (this.returnValueHandlers.getReturnValueHandlers().isEmpty()) {
240                        this.returnValueHandlers.addHandlers(initReturnValueHandlers());
241                }
242                Log returnValueLogger = getReturnValueHandlerLogger();
243                if (returnValueLogger != null) {
244                        this.returnValueHandlers.setLogger(returnValueLogger);
245                }
246
247                this.handlerMethodLogger = getHandlerMethodLogger();
248
249                ApplicationContext context = getApplicationContext();
250                if (context == null) {
251                        return;
252                }
253                for (String beanName : context.getBeanNamesForType(Object.class)) {
254                        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
255                                Class<?> beanType = null;
256                                try {
257                                        beanType = context.getType(beanName);
258                                }
259                                catch (Throwable ex) {
260                                        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
261                                        if (logger.isDebugEnabled()) {
262                                                logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
263                                        }
264                                }
265                                if (beanType != null && isHandler(beanType)) {
266                                        detectHandlerMethods(beanName);
267                                }
268                        }
269                }
270        }
271
272        /**
273         * Return the list of argument resolvers to use. Invoked only if the resolvers
274         * have not already been set via {@link #setArgumentResolvers}.
275         * <p>Subclasses should also take into account custom argument types configured via
276         * {@link #setCustomArgumentResolvers}.
277         */
278        protected abstract List<? extends HandlerMethodArgumentResolver> initArgumentResolvers();
279
280        /**
281         * Return the list of return value handlers to use. Invoked only if the return
282         * value handlers have not already been set via {@link #setReturnValueHandlers}.
283         * <p>Subclasses should also take into account custom return value types configured
284         * via {@link #setCustomReturnValueHandlers}.
285         */
286        protected abstract List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers();
287
288
289        /**
290         * Whether the given bean type should be introspected for messaging handling methods.
291         */
292        protected abstract boolean isHandler(Class<?> beanType);
293
294        /**
295         * Detect if the given handler has any methods that can handle messages and if
296         * so register it with the extracted mapping information.
297         * @param handler the handler to check, either an instance of a Spring bean name
298         */
299        protected final void detectHandlerMethods(final Object handler) {
300                Class<?> handlerType;
301                if (handler instanceof String) {
302                        ApplicationContext context = getApplicationContext();
303                        Assert.state(context != null, "ApplicationContext is required for resolving handler bean names");
304                        handlerType = context.getType((String) handler);
305                }
306                else {
307                        handlerType = handler.getClass();
308                }
309
310                if (handlerType != null) {
311                        final Class<?> userType = ClassUtils.getUserClass(handlerType);
312                        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
313                                        (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
314                        if (logger.isDebugEnabled()) {
315                                logger.debug(formatMappings(userType, methods));
316                        }
317                        methods.forEach((key, value) -> registerHandlerMethod(handler, key, value));
318                }
319        }
320
321        private String formatMappings(Class<?> userType, Map<Method, T> methods) {
322                String formattedType = Arrays.stream(ClassUtils.getPackageName(userType).split("\\."))
323                                .map(p -> p.substring(0, 1))
324                                .collect(Collectors.joining(".", "", "." + userType.getSimpleName()));
325                Function<Method, String> methodFormatter = method -> Arrays.stream(method.getParameterTypes())
326                                .map(Class::getSimpleName)
327                                .collect(Collectors.joining(",", "(", ")"));
328                return methods.entrySet().stream()
329                                .map(e -> {
330                                        Method method = e.getKey();
331                                        return e.getValue() + ": " + method.getName() + methodFormatter.apply(method);
332                                })
333                                .collect(Collectors.joining("\n\t", "\n\t" + formattedType + ":" + "\n\t", ""));
334        }
335
336        /**
337         * Provide the mapping for a handler method.
338         * @param method the method to provide a mapping for
339         * @param handlerType the handler type, possibly a sub-type of the method's declaring class
340         * @return the mapping, or {@code null} if the method is not mapped
341         */
342        @Nullable
343        protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
344
345        /**
346         * Register a handler method and its unique mapping.
347         * @param handler the bean name of the handler or the handler instance
348         * @param method the method to register
349         * @param mapping the mapping conditions associated with the handler method
350         * @throws IllegalStateException if another method was already registered
351         * under the same mapping
352         */
353        protected void registerHandlerMethod(Object handler, Method method, T mapping) {
354                Assert.notNull(mapping, "Mapping must not be null");
355                HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
356                HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
357
358                if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
359                        throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
360                                        "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
361                                        oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
362                }
363
364                this.handlerMethods.put(mapping, newHandlerMethod);
365
366                for (String pattern : getDirectLookupDestinations(mapping)) {
367                        this.destinationLookup.add(pattern, mapping);
368                }
369        }
370
371        /**
372         * Create a HandlerMethod instance from an Object handler that is either a handler
373         * instance or a String-based bean name.
374         */
375        protected HandlerMethod createHandlerMethod(Object handler, Method method) {
376                HandlerMethod handlerMethod;
377                if (handler instanceof String) {
378                        ApplicationContext context = getApplicationContext();
379                        Assert.state(context != null, "ApplicationContext is required for resolving handler bean names");
380                        String beanName = (String) handler;
381                        handlerMethod = new HandlerMethod(beanName, context.getAutowireCapableBeanFactory(), method);
382                }
383                else {
384                        handlerMethod = new HandlerMethod(handler, method);
385                }
386                return handlerMethod;
387        }
388
389        /**
390         * Return destinations contained in the mapping that are not patterns and are
391         * therefore suitable for direct lookups.
392         */
393        protected abstract Set<String> getDirectLookupDestinations(T mapping);
394
395        /**
396         * Return a logger to set on {@link HandlerMethodReturnValueHandlerComposite}.
397         * @since 5.1
398         */
399        @Nullable
400        protected Log getReturnValueHandlerLogger() {
401                return null;
402        }
403
404        /**
405         * Return a logger to set on {@link InvocableHandlerMethod}.
406         * @since 5.1
407         */
408        @Nullable
409        protected Log getHandlerMethodLogger() {
410                return null;
411        }
412
413        /**
414         * Subclasses can invoke this method to populate the MessagingAdviceBean cache
415         * (e.g. to support "global" {@code @MessageExceptionHandler}).
416         * @since 4.2
417         */
418        protected void registerExceptionHandlerAdvice(
419                        MessagingAdviceBean bean, AbstractExceptionHandlerMethodResolver resolver) {
420
421                this.exceptionHandlerAdviceCache.put(bean, resolver);
422        }
423
424        /**
425         * Return a map with all handler methods and their mappings.
426         */
427        public Map<T, HandlerMethod> getHandlerMethods() {
428                return Collections.unmodifiableMap(this.handlerMethods);
429        }
430
431
432        @Override
433        public void handleMessage(Message<?> message) throws MessagingException {
434                String destination = getDestination(message);
435                if (destination == null) {
436                        return;
437                }
438                String lookupDestination = getLookupDestination(destination);
439                if (lookupDestination == null) {
440                        return;
441                }
442
443                MessageHeaderAccessor headerAccessor = MessageHeaderAccessor.getMutableAccessor(message);
444                headerAccessor.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, lookupDestination);
445                headerAccessor.setLeaveMutable(true);
446                message = MessageBuilder.createMessage(message.getPayload(), headerAccessor.getMessageHeaders());
447
448                if (logger.isDebugEnabled()) {
449                        logger.debug("Searching methods to handle " +
450                                        headerAccessor.getShortLogMessage(message.getPayload()) +
451                                        ", lookupDestination='" + lookupDestination + "'");
452                }
453
454                handleMessageInternal(message, lookupDestination);
455                headerAccessor.setImmutable();
456        }
457
458        @Nullable
459        protected abstract String getDestination(Message<?> message);
460
461        /**
462         * Check whether the given destination (of an incoming message) matches to
463         * one of the configured destination prefixes and if so return the remaining
464         * portion of the destination after the matched prefix.
465         * <p>If there are no matching prefixes, return {@code null}.
466         * <p>If there are no destination prefixes, return the destination as is.
467         */
468        @SuppressWarnings("ForLoopReplaceableByForEach")
469        @Nullable
470        protected String getLookupDestination(@Nullable String destination) {
471                if (destination == null) {
472                        return null;
473                }
474                if (CollectionUtils.isEmpty(this.destinationPrefixes)) {
475                        return destination;
476                }
477                for (int i = 0; i < this.destinationPrefixes.size(); i++) {
478                        String prefix = this.destinationPrefixes.get(i);
479                        if (destination.startsWith(prefix)) {
480                                return destination.substring(prefix.length());
481                        }
482                }
483                return null;
484        }
485
486        protected void handleMessageInternal(Message<?> message, String lookupDestination) {
487                List<Match> matches = new ArrayList<>();
488
489                List<T> mappingsByUrl = this.destinationLookup.get(lookupDestination);
490                if (mappingsByUrl != null) {
491                        addMatchesToCollection(mappingsByUrl, message, matches);
492                }
493                if (matches.isEmpty()) {
494                        // No direct hits, go through all mappings
495                        Set<T> allMappings = this.handlerMethods.keySet();
496                        addMatchesToCollection(allMappings, message, matches);
497                }
498                if (matches.isEmpty()) {
499                        handleNoMatch(this.handlerMethods.keySet(), lookupDestination, message);
500                        return;
501                }
502
503                Comparator<Match> comparator = new MatchComparator(getMappingComparator(message));
504                matches.sort(comparator);
505                if (logger.isTraceEnabled()) {
506                        logger.trace("Found " + matches.size() + " handler methods: " + matches);
507                }
508
509                Match bestMatch = matches.get(0);
510                if (matches.size() > 1) {
511                        Match secondBestMatch = matches.get(1);
512                        if (comparator.compare(bestMatch, secondBestMatch) == 0) {
513                                Method m1 = bestMatch.handlerMethod.getMethod();
514                                Method m2 = secondBestMatch.handlerMethod.getMethod();
515                                throw new IllegalStateException("Ambiguous handler methods mapped for destination '" +
516                                                lookupDestination + "': {" + m1 + ", " + m2 + "}");
517                        }
518                }
519
520                handleMatch(bestMatch.mapping, bestMatch.handlerMethod, lookupDestination, message);
521        }
522
523        private void addMatchesToCollection(Collection<T> mappingsToCheck, Message<?> message, List<Match> matches) {
524                for (T mapping : mappingsToCheck) {
525                        T match = getMatchingMapping(mapping, message);
526                        if (match != null) {
527                                matches.add(new Match(match, this.handlerMethods.get(mapping)));
528                        }
529                }
530        }
531
532        /**
533         * Check if a mapping matches the current message and return a possibly
534         * new mapping with conditions relevant to the current request.
535         * @param mapping the mapping to get a match for
536         * @param message the message being handled
537         * @return the match or {@code null} if there is no match
538         */
539        @Nullable
540        protected abstract T getMatchingMapping(T mapping, Message<?> message);
541
542        protected void handleNoMatch(Set<T> ts, String lookupDestination, Message<?> message) {
543                logger.debug("No matching message handler methods.");
544        }
545
546        /**
547         * Return a comparator for sorting matching mappings.
548         * The returned comparator should sort 'better' matches higher.
549         * @param message the current Message
550         * @return the comparator, never {@code null}
551         */
552        protected abstract Comparator<T> getMappingComparator(Message<?> message);
553
554        protected void handleMatch(T mapping, HandlerMethod handlerMethod, String lookupDestination, Message<?> message) {
555                if (logger.isDebugEnabled()) {
556                        logger.debug("Invoking " + handlerMethod.getShortLogMessage());
557                }
558                handlerMethod = handlerMethod.createWithResolvedBean();
559                InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
560                if (this.handlerMethodLogger != null) {
561                        invocable.setLogger(this.handlerMethodLogger);
562                }
563                invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
564                try {
565                        Object returnValue = invocable.invoke(message);
566                        MethodParameter returnType = handlerMethod.getReturnType();
567                        if (void.class == returnType.getParameterType()) {
568                                return;
569                        }
570                        if (returnValue != null && this.returnValueHandlers.isAsyncReturnValue(returnValue, returnType)) {
571                                ListenableFuture<?> future = this.returnValueHandlers.toListenableFuture(returnValue, returnType);
572                                if (future != null) {
573                                        future.addCallback(new ReturnValueListenableFutureCallback(invocable, message));
574                                }
575                        }
576                        else {
577                                this.returnValueHandlers.handleReturnValue(returnValue, returnType, message);
578                        }
579                }
580                catch (Exception ex) {
581                        processHandlerMethodException(handlerMethod, ex, message);
582                }
583                catch (Throwable ex) {
584                        Exception handlingException =
585                                        new MessageHandlingException(message, "Unexpected handler method invocation error", ex);
586                        processHandlerMethodException(handlerMethod, handlingException, message);
587                }
588        }
589
590        protected void processHandlerMethodException(HandlerMethod handlerMethod, Exception exception, Message<?> message) {
591                InvocableHandlerMethod invocable = getExceptionHandlerMethod(handlerMethod, exception);
592                if (invocable == null) {
593                        logger.error("Unhandled exception from message handler method", exception);
594                        return;
595                }
596                invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
597                if (logger.isDebugEnabled()) {
598                        logger.debug("Invoking " + invocable.getShortLogMessage());
599                }
600                try {
601                        Throwable cause = exception.getCause();
602                        Object returnValue = (cause != null ?
603                                        invocable.invoke(message, exception, cause, handlerMethod) :
604                                        invocable.invoke(message, exception, handlerMethod));
605                        MethodParameter returnType = invocable.getReturnType();
606                        if (void.class == returnType.getParameterType()) {
607                                return;
608                        }
609                        this.returnValueHandlers.handleReturnValue(returnValue, returnType, message);
610                }
611                catch (Throwable ex2) {
612                        logger.error("Error while processing handler method exception", ex2);
613                }
614        }
615
616        /**
617         * Find an {@code @MessageExceptionHandler} method for the given exception.
618         * The default implementation searches methods in the class hierarchy of the
619         * HandlerMethod first and if not found, it continues searching for additional
620         * {@code @MessageExceptionHandler} methods among the configured
621         * {@linkplain org.springframework.messaging.handler.MessagingAdviceBean
622         * MessagingAdviceBean}, if any.
623         * @param handlerMethod the method where the exception was raised
624         * @param exception the raised exception
625         * @return a method to handle the exception, or {@code null}
626         * @since 4.2
627         */
628        @Nullable
629        protected InvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
630                if (logger.isDebugEnabled()) {
631                        logger.debug("Searching methods to handle " + exception.getClass().getSimpleName());
632                }
633                Class<?> beanType = handlerMethod.getBeanType();
634                AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
635                if (resolver == null) {
636                        resolver = createExceptionHandlerMethodResolverFor(beanType);
637                        this.exceptionHandlerCache.put(beanType, resolver);
638                }
639                Method method = resolver.resolveMethod(exception);
640                if (method != null) {
641                        return new InvocableHandlerMethod(handlerMethod.getBean(), method);
642                }
643                for (Map.Entry<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
644                        MessagingAdviceBean advice = entry.getKey();
645                        if (advice.isApplicableToBeanType(beanType)) {
646                                resolver = entry.getValue();
647                                method = resolver.resolveMethod(exception);
648                                if (method != null) {
649                                        return new InvocableHandlerMethod(advice.resolveBean(), method);
650                                }
651                        }
652                }
653                return null;
654        }
655
656        protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(
657                        Class<?> beanType);
658
659
660        @Override
661        public String toString() {
662                return getClass().getSimpleName() + "[prefixes=" + getDestinationPrefixes() + "]";
663        }
664
665
666        /**
667         * A thin wrapper around a matched HandlerMethod and its matched mapping for
668         * the purpose of comparing the best match with a comparator in the context
669         * of a message.
670         */
671        private class Match {
672
673                private final T mapping;
674
675                private final HandlerMethod handlerMethod;
676
677                public Match(T mapping, HandlerMethod handlerMethod) {
678                        this.mapping = mapping;
679                        this.handlerMethod = handlerMethod;
680                }
681
682                @Override
683                public String toString() {
684                        return this.mapping.toString();
685                }
686        }
687
688
689        private class MatchComparator implements Comparator<Match> {
690
691                private final Comparator<T> comparator;
692
693                public MatchComparator(Comparator<T> comparator) {
694                        this.comparator = comparator;
695                }
696
697                @Override
698                public int compare(Match match1, Match match2) {
699                        return this.comparator.compare(match1.mapping, match2.mapping);
700                }
701        }
702
703
704        private class ReturnValueListenableFutureCallback implements ListenableFutureCallback<Object> {
705
706                private final InvocableHandlerMethod handlerMethod;
707
708                private final Message<?> message;
709
710                public ReturnValueListenableFutureCallback(InvocableHandlerMethod handlerMethod, Message<?> message) {
711                        this.handlerMethod = handlerMethod;
712                        this.message = message;
713                }
714
715                @Override
716                public void onSuccess(@Nullable Object result) {
717                        try {
718                                MethodParameter returnType = this.handlerMethod.getAsyncReturnValueType(result);
719                                returnValueHandlers.handleReturnValue(result, returnType, this.message);
720                        }
721                        catch (Throwable ex) {
722                                handleFailure(ex);
723                        }
724                }
725
726                @Override
727                public void onFailure(Throwable ex) {
728                        handleFailure(ex);
729                }
730
731                private void handleFailure(Throwable ex) {
732                        Exception cause = (ex instanceof Exception ? (Exception) ex : new IllegalStateException(ex));
733                        processHandlerMethodException(this.handlerMethod, cause, this.message);
734                }
735        }
736
737}