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