Package org.springframework.web.servlet.handler

Source Code of org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MatchComparator

/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.web.servlet.handler;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.HandlerMapping;

/**
* Abstract base class for {@link HandlerMapping} implementations that define a
* mapping between a request and a {@link HandlerMethod}.
*
* <p>For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code <T>}.
*
* @param <T> The mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to incoming request.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

  private boolean detectHandlerMethodsInAncestorContexts = false;

  private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();

  private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();


  /**
   * Whether to detect handler methods in beans in ancestor ApplicationContexts.
   * <p>Default is "false": Only beans in the current ApplicationContext are
   * considered, i.e. only in the context that this HandlerMapping itself
   * is defined in (typically the current DispatcherServlet's context).
   * <p>Switch this flag on to detect handler beans in ancestor contexts
   * (typically the Spring root WebApplicationContext) as well.
   */
  public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) {
    this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
  }

  /**
   * Return a map with all handler methods and their mappings.
   */
  public Map<T, HandlerMethod> getHandlerMethods() {
    return Collections.unmodifiableMap(this.handlerMethods);
  }

  /**
   * Detects handler methods at initialization.
   */
  public void afterPropertiesSet() {
    initHandlerMethods();
  }

  /**
   * Scan beans in the ApplicationContext, detect and register handler methods.
   * @see #isHandler(Class)
   * @see #getMappingForMethod(Method, Class)
   * @see #handlerMethodsInitialized(Map)
   */
  protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
      logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }

    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
        getApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) {
      if (isHandler(getApplicationContext().getType(beanName))){
        detectHandlerMethods(beanName);
      }
    }
    handlerMethodsInitialized(getHandlerMethods());
  }

  /**
   * Whether the given type is a handler with handler methods.
   * @param beanType the type of the bean being checked
   * @return "true" if this a handler type, "false" otherwise.
   */
  protected abstract boolean isHandler(Class<?> beanType);

  /**
   * Invoked after all handler methods have been detected.
   * @param handlerMethods a read-only map with handler methods and mappings.
   */
  protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
  }

  /**
   * Look for handler methods in a handler.
   * @param handler the bean name of a handler or a handler instance
   */
  protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType = (handler instanceof String) ?
        getApplicationContext().getType((String) handler) : handler.getClass();

    final Class<?> userType = ClassUtils.getUserClass(handlerType);

    Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
      public boolean matches(Method method) {
        return getMappingForMethod(method, userType) != null;
      }
    });

    for (Method method : methods) {
      T mapping = getMappingForMethod(method, userType);
      registerHandlerMethod(handler, method, mapping);
    }
  }

  /**
   * Provide the mapping for a handler method. A method for which no
   * mapping can be provided is not a handler method.
   * @param method the method to provide a mapping for
   * @param handlerType the handler type, possibly a sub-type of the method's
   * declaring class
   * @return the mapping, or {@code null} if the method is not mapped
   */
  protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

  /**
   * Register a handler method and its unique mapping.
   * @param handler the bean name of the handler or the handler instance
   * @param method the method to register
   * @param mapping the mapping conditions associated with the handler method
   * @throws IllegalStateException if another method was already registered
   * under the same mapping
   */
  protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    HandlerMethod handlerMethod;
    if (handler instanceof String) {
      String beanName = (String) handler;
      handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
    }
    else {
      handlerMethod = new HandlerMethod(handler, method);
    }

    HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
    if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
      throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
          + "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '"
          + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
    }

    this.handlerMethods.put(mapping, handlerMethod);
    if (logger.isInfoEnabled()) {
      logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
    }

    Set<String> patterns = getMappingPathPatterns(mapping);
    for (String pattern : patterns) {
      if (!getPathMatcher().isPattern(pattern)) {
        this.urlMap.add(pattern, mapping);
      }
    }
  }

  /**
   * Extract and return the URL paths contained in a mapping.
   */
  protected abstract Set<String> getMappingPathPatterns(T mapping);

  /**
   * Look up a handler method for the given request.
   */
  @Override
  protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
      logger.debug("Looking up handler method for path " + lookupPath);
    }

    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

    if (logger.isDebugEnabled()) {
      if (handlerMethod != null) {
        logger.debug("Returning handler method [" + handlerMethod + "]");
      }
      else {
        logger.debug("Did not find handler method for [" + lookupPath + "]");
      }
    }

    return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null;
  }

  /**
   * Look up the best-matching handler method for the current request.
   * If multiple matches are found, the best match is selected.
   * @param lookupPath mapping lookup path within the current servlet mapping
   * @param request the current request
   * @return the best-matching handler method, or {@code null} if no match
   * @see #handleMatch(Object, String, HttpServletRequest)
   * @see #handleNoMatch(Set, String, HttpServletRequest)
   */
  protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();

    List<T> directPathMatches = this.urlMap.get(lookupPath);
    if (directPathMatches != null) {
      addMatchingMappings(directPathMatches, matches, request);
    }

    if (matches.isEmpty()) {
      // No choice but to go through all mappings
      addMatchingMappings(this.handlerMethods.keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
      Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
      Collections.sort(matches, comparator);

      if (logger.isTraceEnabled()) {
        logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
      }

      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
        Match secondBestMatch = matches.get(1);
        if (comparator.compare(bestMatch, secondBestMatch) == 0) {
          Method m1 = bestMatch.handlerMethod.getMethod();
          Method m2 = secondBestMatch.handlerMethod.getMethod();
          throw new IllegalStateException(
              "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
              m1 + ", " + m2 + "}");
        }
      }

      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
    }
    else {
      return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
    }
  }

  private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
      T match = getMatchingMapping(mapping, request);
      if (match != null) {
        matches.add(new Match(match, handlerMethods.get(mapping)));
      }
    }
  }

  /**
   * Check if a mapping matches the current request and return a (potentially
   * new) mapping with conditions relevant to the current request.
   * @param mapping the mapping to get a match for
   * @param request the current HTTP servlet request
   * @return the match, or {@code null} if the mapping doesn't match
   */
  protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);

  /**
   * Return a comparator for sorting matching mappings.
   * The returned comparator should sort 'better' matches higher.
   * @param request the current request
   * @return the comparator, never {@code null}
   */
  protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);

  /**
   * Invoked when a matching mapping is found.
   * @param mapping the matching mapping
   * @param lookupPath mapping lookup path within the current servlet mapping
   * @param request the current request
   */
  protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
    request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
  }

  /**
   * Invoked when no matching mapping is not found.
   * @param mappings all registered mappings
   * @param lookupPath mapping lookup path within the current servlet mapping
   * @param request the current request
   * @throws ServletException in case of errors
   */
  protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
      throws Exception {

    return null;
  }


  /**
   * A temporary container for a mapping matched to a request.
   */
  private class Match {

    private final T mapping;

    private final HandlerMethod handlerMethod;

    private Match(T mapping, HandlerMethod handlerMethod) {
      this.mapping = mapping;
      this.handlerMethod = handlerMethod;
    }

    @Override
    public String toString() {
      return this.mapping.toString();
    }
  }


  private class MatchComparator implements Comparator<Match> {

    private final Comparator<T> comparator;

    public MatchComparator(Comparator<T> comparator) {
      this.comparator = comparator;
    }

    public int compare(Match match1, Match match2) {
      return this.comparator.compare(match1.mapping, match2.mapping);
    }
  }

}
TOP

Related Classes of org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MatchComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.