Open Source Repository

Home /spring/spring-web-servlet-3.0.5 | Repository Home



org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
/*
 * Copyright 2002-2010 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.util.ArrayList;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;

/**
 * Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
 * implementations. Provides infrastructure for mapping handlers to URLs and configurable
 * URL lookup. For information on the latter, see "alwaysUseFullPath" property.
 *
 <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
 * various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
 * both "/test" and "/team", "/test/*" matches all paths in the "/test" directory,
 * "/test/**" matches all paths below "/test". For details, see the
 {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
 *
 <p>Will search all path patterns to find the most exact match for the
 * current request path. The most exact match is defined as the longest
 * path pattern that matches the current request path.
 *
 @author Juergen Hoeller
 @author Arjen Poutsma
 @since 16.04.2003
 @see #setAlwaysUseFullPath
 @see #setUrlDecode
 @see org.springframework.util.AntPathMatcher
 */
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {

  private UrlPathHelper urlPathHelper = new UrlPathHelper();

  private PathMatcher pathMatcher = new AntPathMatcher();

  private Object rootHandler;

  private boolean lazyInitHandlers = false;

  private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();

  private MappedInterceptors mappedInterceptors;


  /**
   * Set if URL lookup should always use the full path within the current servlet
   * context. Else, the path within the current servlet mapping is used if applicable
   * (that is, in the case of a ".../*" servlet mapping in web.xml).
   <p>Default is "false".
   @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
   */
  public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
    this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
  }

  /**
   * Set if context path and request URI should be URL-decoded. Both are returned
   <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
   <p>Uses either the request encoding or the default encoding according
   * to the Servlet spec (ISO-8859-1).
   @see org.springframework.web.util.UrlPathHelper#setUrlDecode
   */
  public void setUrlDecode(boolean urlDecode) {
    this.urlPathHelper.setUrlDecode(urlDecode);
  }

  /**
   * Set the UrlPathHelper to use for resolution of lookup paths.
   <p>Use this to override the default UrlPathHelper with a custom subclass,
   * or to share common UrlPathHelper settings across multiple HandlerMappings
   * and MethodNameResolvers.
   @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
   */
  public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
    this.urlPathHelper = urlPathHelper;
  }

  /**
   * Set the PathMatcher implementation to use for matching URL paths
   * against registered URL patterns. Default is AntPathMatcher.
   @see org.springframework.util.AntPathMatcher
   */
  public void setPathMatcher(PathMatcher pathMatcher) {
    Assert.notNull(pathMatcher, "PathMatcher must not be null");
    this.pathMatcher = pathMatcher;
  }

  /**
   * Return the PathMatcher implementation to use for matching URL paths
   * against registered URL patterns.
   */
  public PathMatcher getPathMatcher() {
    return this.pathMatcher;
  }

  /**
   * Set the root handler for this handler mapping, that is,
   * the handler to be registered for the root path ("/").
   <p>Default is <code>null</code>, indicating no root handler.
   */
  public void setRootHandler(Object rootHandler) {
    this.rootHandler = rootHandler;
  }

  /**
   * Return the root handler for this handler mapping (registered for "/"),
   * or <code>null</code> if none.
   */
  public Object getRootHandler() {
    return this.rootHandler;
  }

  /**
   * Set whether to lazily initialize handlers. Only applicable to
   * singleton handlers, as prototypes are always lazily initialized.
   * Default is "false", as eager initialization allows for more efficiency
   * through referencing the controller objects directly.
   <p>If you want to allow your controllers to be lazily initialized,
   * make them "lazy-init" and set this flag to true. Just making them
   * "lazy-init" will not work, as they are initialized through the
   * references from the handler mapping in this case.
   */
  public void setLazyInitHandlers(boolean lazyInitHandlers) {
    this.lazyInitHandlers = lazyInitHandlers;
  }

  public void setMappedInterceptors(MappedInterceptor[] mappedInterceptors) {
    this.mappedInterceptors = new MappedInterceptors(mappedInterceptors);
  }

  
  @Override
  protected void initInterceptors() {
    super.initInterceptors();
    Map<String, MappedInterceptor> mappedInterceptors = BeanFactoryUtils.beansOfTypeIncludingAncestors(
        getApplicationContext(), MappedInterceptor.class, true, false);
    if (!mappedInterceptors.isEmpty()) {
      this.mappedInterceptors = new MappedInterceptors(mappedInterceptors.values().toArray(
          new MappedInterceptor[mappedInterceptors.size()]));
    }

  }

  /**
   * Look up a handler for the URL path of the given request.
   @param request current HTTP request
   @return the handler instance, or <code>null</code> if none found
   */
  @Override
  protected Object getHandlerInternal(HttpServletRequest requestthrows Exception {
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
      // We need to care for the default handler directly, since we need to
      // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
      Object rawHandler = null;
      if ("/".equals(lookupPath)) {
        rawHandler = getRootHandler();
      }
      if (rawHandler == null) {
        rawHandler = getDefaultHandler();
      }
      if (rawHandler != null) {
        // Bean name or resolved handler?
        if (rawHandler instanceof String) {
          String handlerName = (StringrawHandler;
          rawHandler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(rawHandler, request);
        handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
      }
    }
    if (handler != null && this.mappedInterceptors != null) {
      Set<HandlerInterceptor> mappedInterceptors =
          this.mappedInterceptors.getInterceptors(lookupPath, this.pathMatcher);
      if (!mappedInterceptors.isEmpty()) {
        HandlerExecutionChain chain;
        if (handler instanceof HandlerExecutionChain) {
          chain = (HandlerExecutionChainhandler;
        }
        else {
          chain = new HandlerExecutionChain(handler);
        }
        chain.addInterceptors(mappedInterceptors.toArray(new HandlerInterceptor[mappedInterceptors.size()]));
      }
    }
    if (handler != null && logger.isDebugEnabled()) {
      logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
      logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    return handler;
  }

  /**
   * Look up a handler instance for the given URL path.
   <p>Supports direct matches, e.g. a registered "/test" matches "/test",
   * and various Ant-style pattern matches, e.g. a registered "/t*" matches
   * both "/test" and "/team". For details, see the AntPathMatcher class.
   <p>Looks for the most exact pattern, where most exact is defined as
   * the longest path pattern.
   @param urlPath URL the bean is mapped to
   @param request current HTTP request (to expose the path within the mapping to)
   @return the associated handler instance, or <code>null</code> if not found
   @see #exposePathWithinMapping
   @see org.springframework.util.AntPathMatcher
   */
  protected Object lookupHandler(String urlPath, HttpServletRequest requestthrows Exception {
    // Direct match?
    Object handler = this.handlerMap.get(urlPath);
    if (handler != null) {
      // Bean name or resolved handler?
      if (handler instanceof String) {
        String handlerName = (Stringhandler;
        handler = getApplicationContext().getBean(handlerName);
      }
      validateHandler(handler, request);
      return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }
    // Pattern match?
    List<String> matchingPatterns = new ArrayList<String>();
    for (String registeredPattern : this.handlerMap.keySet()) {
      if (getPathMatcher().match(registeredPattern, urlPath)) {
        matchingPatterns.add(registeredPattern);
      }
    }
    String bestPatternMatch = null;
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if (!matchingPatterns.isEmpty()) {
      Collections.sort(matchingPatterns, patternComparator);
      if (logger.isDebugEnabled()) {
        logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
      }
      bestPatternMatch = matchingPatterns.get(0);
    }
    if (bestPatternMatch != null) {
      handler = this.handlerMap.get(bestPatternMatch);
      // Bean name or resolved handler?
      if (handler instanceof String) {
        String handlerName = (Stringhandler;
        handler = getApplicationContext().getBean(handlerName);
      }
      validateHandler(handler, request);
      String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

      // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
      // for all of them
      Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
      for (String matchingPattern : matchingPatterns) {
        if (patternComparator.compare(bestPatternMatch, matchingPattern== 0) {
          uriTemplateVariables
              .putAll(getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath));
        }
      }
      if (logger.isDebugEnabled()) {
        logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
      }
      return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
    }
    // No handler found...
    return null;
  }

  /**
   * Validate the given handler against the current request.
   <p>The default implementation is empty. Can be overridden in subclasses,
   * for example to enforce specific preconditions expressed in URL mappings.
   @param handler the handler object to validate
   @param request current HTTP request
   @throws Exception if validation failed
   */
  protected void validateHandler(Object handler, HttpServletRequest requestthrows Exception {
  }

  /**
   * Build a handler object for the given raw handler, exposing the actual
   * handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
   * the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
   <p>The default implementation builds a {@link HandlerExecutionChain}
   * with a special interceptor that exposes the path attribute and uri template variables
   @param rawHandler the raw handler to expose
   @param pathWithinMapping the path to expose before executing the handler
   @param uriTemplateVariables the URI template variables, can be <code>null</code> if no variables found
   @return the final handler object
   */
  protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
      String pathWithinMapping, Map<String, String> uriTemplateVariables) {

    HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
    chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
    if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
      chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
    }
    return chain;
  }

  /**
   * Expose the path within the current mapping as request attribute.
   @param pathWithinMapping the path within the current mapping
   @param request the request to expose the path to
   @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
   */
  protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping, HttpServletRequest request) {
    request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
    request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
  }

  /**
   * Expose the URI templates variables as request attribute.
   @param uriTemplateVariables the URI template variables
   @param request the request to expose the path to
   @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
   */
  protected void exposeUriTemplateVariables(Map<String, String> uriTemplateVariables, HttpServletRequest request) {
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
  }

  /**
   * Register the specified handler for the given URL paths.
   @param urlPaths the URLs that the bean should be mapped to
   @param beanName the name of the handler bean
   @throws BeansException if the handler couldn't be registered
   @throws IllegalStateException if there is a conflicting handler registered
   */
  protected void registerHandler(String[] urlPaths, String beanNamethrows BeansException, IllegalStateException {
    Assert.notNull(urlPaths, "URL path array must not be null");
    for (String urlPath : urlPaths) {
      registerHandler(urlPath, beanName);
    }
  }

  /**
   * Register the specified handler for the given URL path.
   @param urlPath the URL the bean should be mapped to
   @param handler the handler instance or handler bean name String
   * (a bean name will automatically be resolved into the corresponding handler bean)
   @throws BeansException if the handler couldn't be registered
   @throws IllegalStateException if there is a conflicting handler registered
   */
  protected void registerHandler(String urlPath, Object handlerthrows BeansException, IllegalStateException {
    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {
      String handlerName = (Stringhandler;
      if (getApplicationContext().isSingleton(handlerName)) {
        resolvedHandler = getApplicationContext().getBean(handlerName);
      }
    }

    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
      if (mappedHandler != resolvedHandler) {
        throw new IllegalStateException(
            "Cannot map " + getHandlerDescription(handler" to URL path [" + urlPath +
            "]: There is already " + getHandlerDescription(mappedHandler" mapped.");
      }
    }
    else {
      if (urlPath.equals("/")) {
        if (logger.isInfoEnabled()) {
          logger.info("Root mapping to " + getHandlerDescription(handler));
        }
        setRootHandler(resolvedHandler);
      }
      else if (urlPath.equals("/*")) {
        if (logger.isInfoEnabled()) {
          logger.info("Default mapping to " + getHandlerDescription(handler));
        }
        setDefaultHandler(resolvedHandler);
      }
      else {
        this.handlerMap.put(urlPath, resolvedHandler);
        if (logger.isInfoEnabled()) {
          logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
        }
      }
    }
  }

  private String getHandlerDescription(Object handler) {
    return "handler " (handler instanceof String ? "'" + handler + "'" "of type [" + handler.getClass() "]");
  }


  /**
   * Return the registered handlers as an unmodifiable Map, with the registered path
   * as key and the handler object (or handler bean name in case of a lazy-init handler)
   * as value.
   @see #getDefaultHandler()
   */
  public final Map<String, Object> getHandlerMap() {
    return Collections.unmodifiableMap(this.handlerMap);
  }


  /**
   * Special interceptor for exposing the
   {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
   @link AbstractUrlHandlerMapping#exposePathWithinMapping
   */
  private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {

    private final String bestMatchingPattern;

    private final String pathWithinMapping;

    public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
      this.bestMatchingPattern = bestMatchingPattern;
      this.pathWithinMapping = pathWithinMapping;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
      return true;
    }
  }


  /**
   * Special interceptor for exposing the
   {@link AbstractUrlHandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE} attribute.
   @link AbstractUrlHandlerMapping#exposePathWithinMapping
   */
  private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {

    private final Map<String, String> uriTemplateVariables;

    public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
      this.uriTemplateVariables = uriTemplateVariables;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      exposeUriTemplateVariables(this.uriTemplateVariables, request);
      return true;
    }
  }

}