001/*
002 * Copyright 2002-2017 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.web.servlet.handler;
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.HashMap;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.locks.ReentrantReadWriteLock;
031import javax.servlet.ServletException;
032import javax.servlet.http.HttpServletRequest;
033
034import org.springframework.aop.support.AopUtils;
035import org.springframework.beans.factory.BeanFactoryUtils;
036import org.springframework.beans.factory.InitializingBean;
037import org.springframework.core.MethodIntrospector;
038import org.springframework.util.Assert;
039import org.springframework.util.ClassUtils;
040import org.springframework.util.LinkedMultiValueMap;
041import org.springframework.util.MultiValueMap;
042import org.springframework.web.cors.CorsConfiguration;
043import org.springframework.web.cors.CorsUtils;
044import org.springframework.web.method.HandlerMethod;
045import org.springframework.web.servlet.HandlerMapping;
046
047/**
048 * Abstract base class for {@link HandlerMapping} implementations that define
049 * a mapping between a request and a {@link HandlerMethod}.
050 *
051 * <p>For each registered handler method, a unique mapping is maintained with
052 * subclasses defining the details of the mapping type {@code <T>}.
053 *
054 * @author Arjen Poutsma
055 * @author Rossen Stoyanchev
056 * @author Juergen Hoeller
057 * @since 3.1
058 * @param <T> The mapping for a {@link HandlerMethod} containing the conditions
059 * needed to match the handler method to incoming request.
060 */
061public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
062
063        /**
064         * Bean name prefix for target beans behind scoped proxies. Used to exclude those
065         * targets from handler method detection, in favor of the corresponding proxies.
066         * <p>We're not checking the autowire-candidate status here, which is how the
067         * proxy target filtering problem is being handled at the autowiring level,
068         * since autowire-candidate may have been turned to {@code false} for other
069         * reasons, while still expecting the bean to be eligible for handler methods.
070         * <p>Originally defined in {@link org.springframework.aop.scope.ScopedProxyUtils}
071         * but duplicated here to avoid a hard dependency on the spring-aop module.
072         */
073        private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
074
075        private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
076                        new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
077
078        private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
079
080        static {
081                ALLOW_CORS_CONFIG.addAllowedOrigin("*");
082                ALLOW_CORS_CONFIG.addAllowedMethod("*");
083                ALLOW_CORS_CONFIG.addAllowedHeader("*");
084                ALLOW_CORS_CONFIG.setAllowCredentials(true);
085        }
086
087
088        private boolean detectHandlerMethodsInAncestorContexts = false;
089
090        private HandlerMethodMappingNamingStrategy<T> namingStrategy;
091
092        private final MappingRegistry mappingRegistry = new MappingRegistry();
093
094
095        /**
096         * Whether to detect handler methods in beans in ancestor ApplicationContexts.
097         * <p>Default is "false": Only beans in the current ApplicationContext are
098         * considered, i.e. only in the context that this HandlerMapping itself
099         * is defined in (typically the current DispatcherServlet's context).
100         * <p>Switch this flag on to detect handler beans in ancestor contexts
101         * (typically the Spring root WebApplicationContext) as well.
102         */
103        public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) {
104                this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
105        }
106
107        /**
108         * Configure the naming strategy to use for assigning a default name to every
109         * mapped handler method.
110         * <p>The default naming strategy is based on the capital letters of the
111         * class name followed by "#" and then the method name, e.g. "TC#getFoo"
112         * for a class named TestController with method getFoo.
113         */
114        public void setHandlerMethodMappingNamingStrategy(HandlerMethodMappingNamingStrategy<T> namingStrategy) {
115                this.namingStrategy = namingStrategy;
116        }
117
118        /**
119         * Return the configured naming strategy or {@code null}.
120         */
121        public HandlerMethodMappingNamingStrategy<T> getNamingStrategy() {
122                return this.namingStrategy;
123        }
124
125        /**
126         * Return a (read-only) map with all mappings and HandlerMethod's.
127         */
128        public Map<T, HandlerMethod> getHandlerMethods() {
129                this.mappingRegistry.acquireReadLock();
130                try {
131                        return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
132                }
133                finally {
134                        this.mappingRegistry.releaseReadLock();
135                }
136        }
137
138        /**
139         * Return the handler methods for the given mapping name.
140         * @param mappingName the mapping name
141         * @return a list of matching HandlerMethod's or {@code null}; the returned
142         * list will never be modified and is safe to iterate.
143         * @see #setHandlerMethodMappingNamingStrategy
144         */
145        public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) {
146                return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName);
147        }
148
149        /**
150         * Return the internal mapping registry. Provided for testing purposes.
151         */
152        MappingRegistry getMappingRegistry() {
153                return this.mappingRegistry;
154        }
155
156        /**
157         * Register the given mapping.
158         * <p>This method may be invoked at runtime after initialization has completed.
159         * @param mapping the mapping for the handler method
160         * @param handler the handler
161         * @param method the method
162         */
163        public void registerMapping(T mapping, Object handler, Method method) {
164                this.mappingRegistry.register(mapping, handler, method);
165        }
166
167        /**
168         * Un-register the given mapping.
169         * <p>This method may be invoked at runtime after initialization has completed.
170         * @param mapping the mapping to unregister
171         */
172        public void unregisterMapping(T mapping) {
173                this.mappingRegistry.unregister(mapping);
174        }
175
176
177        // Handler method detection
178
179        /**
180         * Detects handler methods at initialization.
181         */
182        @Override
183        public void afterPropertiesSet() {
184                initHandlerMethods();
185        }
186
187        /**
188         * Scan beans in the ApplicationContext, detect and register handler methods.
189         * @see #isHandler(Class)
190         * @see #getMappingForMethod(Method, Class)
191         * @see #handlerMethodsInitialized(Map)
192         */
193        protected void initHandlerMethods() {
194                if (logger.isDebugEnabled()) {
195                        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
196                }
197                String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
198                                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
199                                getApplicationContext().getBeanNamesForType(Object.class));
200
201                for (String beanName : beanNames) {
202                        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
203                                Class<?> beanType = null;
204                                try {
205                                        beanType = getApplicationContext().getType(beanName);
206                                }
207                                catch (Throwable ex) {
208                                        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
209                                        if (logger.isDebugEnabled()) {
210                                                logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
211                                        }
212                                }
213                                if (beanType != null && isHandler(beanType)) {
214                                        detectHandlerMethods(beanName);
215                                }
216                        }
217                }
218                handlerMethodsInitialized(getHandlerMethods());
219        }
220
221        /**
222         * Look for handler methods in a handler.
223         * @param handler the bean name of a handler or a handler instance
224         */
225        protected void detectHandlerMethods(final Object handler) {
226                Class<?> handlerType = (handler instanceof String ?
227                                getApplicationContext().getType((String) handler) : handler.getClass());
228                final Class<?> userType = ClassUtils.getUserClass(handlerType);
229
230                Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
231                                new MethodIntrospector.MetadataLookup<T>() {
232                                        @Override
233                                        public T inspect(Method method) {
234                                                try {
235                                                        return getMappingForMethod(method, userType);
236                                                }
237                                                catch (Throwable ex) {
238                                                        throw new IllegalStateException("Invalid mapping on handler class [" +
239                                                                        userType.getName() + "]: " + method, ex);
240                                                }
241                                        }
242                                });
243
244                if (logger.isDebugEnabled()) {
245                        logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
246                }
247                for (Map.Entry<Method, T> entry : methods.entrySet()) {
248                        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
249                        T mapping = entry.getValue();
250                        registerHandlerMethod(handler, invocableMethod, mapping);
251                }
252        }
253
254        /**
255         * Register a handler method and its unique mapping. Invoked at startup for
256         * each detected handler method.
257         * @param handler the bean name of the handler or the handler instance
258         * @param method the method to register
259         * @param mapping the mapping conditions associated with the handler method
260         * @throws IllegalStateException if another method was already registered
261         * under the same mapping
262         */
263        protected void registerHandlerMethod(Object handler, Method method, T mapping) {
264                this.mappingRegistry.register(mapping, handler, method);
265        }
266
267        /**
268         * Create the HandlerMethod instance.
269         * @param handler either a bean name or an actual handler instance
270         * @param method the target method
271         * @return the created HandlerMethod
272         */
273        protected HandlerMethod createHandlerMethod(Object handler, Method method) {
274                HandlerMethod handlerMethod;
275                if (handler instanceof String) {
276                        String beanName = (String) handler;
277                        handlerMethod = new HandlerMethod(beanName,
278                                        getApplicationContext().getAutowireCapableBeanFactory(), method);
279                }
280                else {
281                        handlerMethod = new HandlerMethod(handler, method);
282                }
283                return handlerMethod;
284        }
285
286        /**
287         * Extract and return the CORS configuration for the mapping.
288         */
289        protected CorsConfiguration initCorsConfiguration(Object handler, Method method, T mapping) {
290                return null;
291        }
292
293        /**
294         * Invoked after all handler methods have been detected.
295         * @param handlerMethods a read-only map with handler methods and mappings.
296         */
297        protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
298        }
299
300
301        // Handler method lookup
302
303        /**
304         * Look up a handler method for the given request.
305         */
306        @Override
307        protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
308                String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
309                if (logger.isDebugEnabled()) {
310                        logger.debug("Looking up handler method for path " + lookupPath);
311                }
312                this.mappingRegistry.acquireReadLock();
313                try {
314                        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
315                        if (logger.isDebugEnabled()) {
316                                if (handlerMethod != null) {
317                                        logger.debug("Returning handler method [" + handlerMethod + "]");
318                                }
319                                else {
320                                        logger.debug("Did not find handler method for [" + lookupPath + "]");
321                                }
322                        }
323                        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
324                }
325                finally {
326                        this.mappingRegistry.releaseReadLock();
327                }
328        }
329
330        /**
331         * Look up the best-matching handler method for the current request.
332         * If multiple matches are found, the best match is selected.
333         * @param lookupPath mapping lookup path within the current servlet mapping
334         * @param request the current request
335         * @return the best-matching handler method, or {@code null} if no match
336         * @see #handleMatch(Object, String, HttpServletRequest)
337         * @see #handleNoMatch(Set, String, HttpServletRequest)
338         */
339        protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
340                List<Match> matches = new ArrayList<Match>();
341                List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
342                if (directPathMatches != null) {
343                        addMatchingMappings(directPathMatches, matches, request);
344                }
345                if (matches.isEmpty()) {
346                        // No choice but to go through all mappings...
347                        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
348                }
349
350                if (!matches.isEmpty()) {
351                        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
352                        Collections.sort(matches, comparator);
353                        if (logger.isTraceEnabled()) {
354                                logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
355                                                lookupPath + "] : " + matches);
356                        }
357                        Match bestMatch = matches.get(0);
358                        if (matches.size() > 1) {
359                                if (CorsUtils.isPreFlightRequest(request)) {
360                                        return PREFLIGHT_AMBIGUOUS_MATCH;
361                                }
362                                Match secondBestMatch = matches.get(1);
363                                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
364                                        Method m1 = bestMatch.handlerMethod.getMethod();
365                                        Method m2 = secondBestMatch.handlerMethod.getMethod();
366                                        throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
367                                                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
368                                }
369                        }
370                        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
371                        handleMatch(bestMatch.mapping, lookupPath, request);
372                        return bestMatch.handlerMethod;
373                }
374                else {
375                        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
376                }
377        }
378
379        private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
380                for (T mapping : mappings) {
381                        T match = getMatchingMapping(mapping, request);
382                        if (match != null) {
383                                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
384                        }
385                }
386        }
387
388        /**
389         * Invoked when a matching mapping is found.
390         * @param mapping the matching mapping
391         * @param lookupPath mapping lookup path within the current servlet mapping
392         * @param request the current request
393         */
394        protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
395                request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
396        }
397
398        /**
399         * Invoked when no matching mapping is not found.
400         * @param mappings all registered mappings
401         * @param lookupPath mapping lookup path within the current servlet mapping
402         * @param request the current request
403         * @throws ServletException in case of errors
404         */
405        protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
406                        throws Exception {
407
408                return null;
409        }
410
411        @Override
412        protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
413                CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
414                if (handler instanceof HandlerMethod) {
415                        HandlerMethod handlerMethod = (HandlerMethod) handler;
416                        if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
417                                return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
418                        }
419                        else {
420                                CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
421                                corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
422                        }
423                }
424                return corsConfig;
425        }
426
427
428        // Abstract template methods
429
430        /**
431         * Whether the given type is a handler with handler methods.
432         * @param beanType the type of the bean being checked
433         * @return "true" if this a handler type, "false" otherwise.
434         */
435        protected abstract boolean isHandler(Class<?> beanType);
436
437        /**
438         * Provide the mapping for a handler method. A method for which no
439         * mapping can be provided is not a handler method.
440         * @param method the method to provide a mapping for
441         * @param handlerType the handler type, possibly a sub-type of the method's
442         * declaring class
443         * @return the mapping, or {@code null} if the method is not mapped
444         */
445        protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
446
447        /**
448         * Extract and return the URL paths contained in a mapping.
449         */
450        protected abstract Set<String> getMappingPathPatterns(T mapping);
451
452        /**
453         * Check if a mapping matches the current request and return a (potentially
454         * new) mapping with conditions relevant to the current request.
455         * @param mapping the mapping to get a match for
456         * @param request the current HTTP servlet request
457         * @return the match, or {@code null} if the mapping doesn't match
458         */
459        protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
460
461        /**
462         * Return a comparator for sorting matching mappings.
463         * The returned comparator should sort 'better' matches higher.
464         * @param request the current request
465         * @return the comparator (never {@code null})
466         */
467        protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
468
469
470        /**
471         * A registry that maintains all mappings to handler methods, exposing methods
472         * to perform lookups and providing concurrent access.
473         *
474         * <p>Package-private for testing purposes.
475         */
476        class MappingRegistry {
477
478                private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();
479
480                private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
481
482                private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
483
484                private final Map<String, List<HandlerMethod>> nameLookup =
485                                new ConcurrentHashMap<String, List<HandlerMethod>>();
486
487                private final Map<HandlerMethod, CorsConfiguration> corsLookup =
488                                new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();
489
490                private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
491
492                /**
493                 * Return all mappings and handler methods. Not thread-safe.
494                 * @see #acquireReadLock()
495                 */
496                public Map<T, HandlerMethod> getMappings() {
497                        return this.mappingLookup;
498                }
499
500                /**
501                 * Return matches for the given URL path. Not thread-safe.
502                 * @see #acquireReadLock()
503                 */
504                public List<T> getMappingsByUrl(String urlPath) {
505                        return this.urlLookup.get(urlPath);
506                }
507
508                /**
509                 * Return handler methods by mapping name. Thread-safe for concurrent use.
510                 */
511                public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
512                        return this.nameLookup.get(mappingName);
513                }
514
515                /**
516                 * Return CORS configuration. Thread-safe for concurrent use.
517                 */
518                public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
519                        HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
520                        return this.corsLookup.get(original != null ? original : handlerMethod);
521                }
522
523                /**
524                 * Acquire the read lock when using getMappings and getMappingsByUrl.
525                 */
526                public void acquireReadLock() {
527                        this.readWriteLock.readLock().lock();
528                }
529
530                /**
531                 * Release the read lock after using getMappings and getMappingsByUrl.
532                 */
533                public void releaseReadLock() {
534                        this.readWriteLock.readLock().unlock();
535                }
536
537                public void register(T mapping, Object handler, Method method) {
538                        this.readWriteLock.writeLock().lock();
539                        try {
540                                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
541                                assertUniqueMethodMapping(handlerMethod, mapping);
542
543                                if (logger.isInfoEnabled()) {
544                                        logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
545                                }
546                                this.mappingLookup.put(mapping, handlerMethod);
547
548                                List<String> directUrls = getDirectUrls(mapping);
549                                for (String url : directUrls) {
550                                        this.urlLookup.add(url, mapping);
551                                }
552
553                                String name = null;
554                                if (getNamingStrategy() != null) {
555                                        name = getNamingStrategy().getName(handlerMethod, mapping);
556                                        addMappingName(name, handlerMethod);
557                                }
558
559                                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
560                                if (corsConfig != null) {
561                                        this.corsLookup.put(handlerMethod, corsConfig);
562                                }
563
564                                this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
565                        }
566                        finally {
567                                this.readWriteLock.writeLock().unlock();
568                        }
569                }
570
571                private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
572                        HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
573                        if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
574                                throw new IllegalStateException(
575                                                "Ambiguous mapping. Cannot map '" +     newHandlerMethod.getBean() + "' method \n" +
576                                                newHandlerMethod + "\nto " + mapping + ": There is already '" +
577                                                handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
578                        }
579                }
580
581                private List<String> getDirectUrls(T mapping) {
582                        List<String> urls = new ArrayList<String>(1);
583                        for (String path : getMappingPathPatterns(mapping)) {
584                                if (!getPathMatcher().isPattern(path)) {
585                                        urls.add(path);
586                                }
587                        }
588                        return urls;
589                }
590
591                private void addMappingName(String name, HandlerMethod handlerMethod) {
592                        List<HandlerMethod> oldList = this.nameLookup.get(name);
593                        if (oldList == null) {
594                                oldList = Collections.<HandlerMethod>emptyList();
595                        }
596
597                        for (HandlerMethod current : oldList) {
598                                if (handlerMethod.equals(current)) {
599                                        return;
600                                }
601                        }
602
603                        if (logger.isTraceEnabled()) {
604                                logger.trace("Mapping name '" + name + "'");
605                        }
606
607                        List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() + 1);
608                        newList.addAll(oldList);
609                        newList.add(handlerMethod);
610                        this.nameLookup.put(name, newList);
611
612                        if (newList.size() > 1) {
613                                if (logger.isTraceEnabled()) {
614                                        logger.trace("Mapping name clash for handlerMethods " + newList +
615                                                        ". Consider assigning explicit names.");
616                                }
617                        }
618                }
619
620                public void unregister(T mapping) {
621                        this.readWriteLock.writeLock().lock();
622                        try {
623                                MappingRegistration<T> definition = this.registry.remove(mapping);
624                                if (definition == null) {
625                                        return;
626                                }
627
628                                this.mappingLookup.remove(definition.getMapping());
629
630                                for (String url : definition.getDirectUrls()) {
631                                        List<T> list = this.urlLookup.get(url);
632                                        if (list != null) {
633                                                list.remove(definition.getMapping());
634                                                if (list.isEmpty()) {
635                                                        this.urlLookup.remove(url);
636                                                }
637                                        }
638                                }
639
640                                removeMappingName(definition);
641
642                                this.corsLookup.remove(definition.getHandlerMethod());
643                        }
644                        finally {
645                                this.readWriteLock.writeLock().unlock();
646                        }
647                }
648
649                private void removeMappingName(MappingRegistration<T> definition) {
650                        String name = definition.getMappingName();
651                        if (name == null) {
652                                return;
653                        }
654                        HandlerMethod handlerMethod = definition.getHandlerMethod();
655                        List<HandlerMethod> oldList = this.nameLookup.get(name);
656                        if (oldList == null) {
657                                return;
658                        }
659                        if (oldList.size() <= 1) {
660                                this.nameLookup.remove(name);
661                                return;
662                        }
663                        List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() - 1);
664                        for (HandlerMethod current : oldList) {
665                                if (!current.equals(handlerMethod)) {
666                                        newList.add(current);
667                                }
668                        }
669                        this.nameLookup.put(name, newList);
670                }
671        }
672
673
674        private static class MappingRegistration<T> {
675
676                private final T mapping;
677
678                private final HandlerMethod handlerMethod;
679
680                private final List<String> directUrls;
681
682                private final String mappingName;
683
684                public MappingRegistration(T mapping, HandlerMethod handlerMethod, List<String> directUrls, String mappingName) {
685                        Assert.notNull(mapping, "Mapping must not be null");
686                        Assert.notNull(handlerMethod, "HandlerMethod must not be null");
687                        this.mapping = mapping;
688                        this.handlerMethod = handlerMethod;
689                        this.directUrls = (directUrls != null ? directUrls : Collections.<String>emptyList());
690                        this.mappingName = mappingName;
691                }
692
693                public T getMapping() {
694                        return this.mapping;
695                }
696
697                public HandlerMethod getHandlerMethod() {
698                        return this.handlerMethod;
699                }
700
701                public List<String> getDirectUrls() {
702                        return this.directUrls;
703                }
704
705                public String getMappingName() {
706                        return this.mappingName;
707                }
708        }
709
710
711        /**
712         * A thin wrapper around a matched HandlerMethod and its mapping, for the purpose of
713         * comparing the best match with a comparator in the context of the current request.
714         */
715        private class Match {
716
717                private final T mapping;
718
719                private final HandlerMethod handlerMethod;
720
721                public Match(T mapping, HandlerMethod handlerMethod) {
722                        this.mapping = mapping;
723                        this.handlerMethod = handlerMethod;
724                }
725
726                @Override
727                public String toString() {
728                        return this.mapping.toString();
729                }
730        }
731
732
733        private class MatchComparator implements Comparator<Match> {
734
735                private final Comparator<T> comparator;
736
737                public MatchComparator(Comparator<T> comparator) {
738                        this.comparator = comparator;
739                }
740
741                @Override
742                public int compare(Match match1, Match match2) {
743                        return this.comparator.compare(match1.mapping, match2.mapping);
744                }
745        }
746
747
748        private static class EmptyHandler {
749
750                public void handle() {
751                        throw new UnsupportedOperationException("Not implemented");
752                }
753        }
754
755}