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}