All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.handler.AbstractUrlHandlerMapping Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2021 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
 *
 *      https://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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * Abstract base class for URL-mapped {@link HandlerMapping} implementations.
 *
 * 

Supports literal matches and pattern matches such as "/test/*", "/test/**", * and others. For details on pattern syntax refer to {@link PathPattern} when * parsed patterns are {@link #usesPathPatterns() enabled} or see * {@link AntPathMatcher} otherwise. The syntax is largely the same but the * {@code PathPattern} syntax is more tailored for web applications, and its * implementation is more efficient. * *

All path patterns are checked in order to find the most exact match for the * current request path where the "most exact" is the longest path pattern that * matches the current request path. * * @author Juergen Hoeller * @author Arjen Poutsma * @since 16.04.2003 */ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping { @Nullable private Object rootHandler; private boolean useTrailingSlashMatch = false; private boolean lazyInitHandlers = false; private final Map handlerMap = new LinkedHashMap<>(); private final Map pathPatternHandlerMap = new LinkedHashMap<>(); @Override public void setPatternParser(PathPatternParser patternParser) { Assert.state(this.handlerMap.isEmpty(), "PathPatternParser must be set before the initialization of " + "the handler map via ApplicationContextAware#setApplicationContext."); super.setPatternParser(patternParser); } /** * Set the root handler for this handler mapping, that is, * the handler to be registered for the root path ("/"). *

Default is {@code null}, indicating no root handler. */ public void setRootHandler(@Nullable Object rootHandler) { this.rootHandler = rootHandler; } /** * Return the root handler for this handler mapping (registered for "/"), * or {@code null} if none. */ @Nullable public Object getRootHandler() { return this.rootHandler; } /** * Whether to match to URLs irrespective of the presence of a trailing slash. * If enabled a URL pattern such as "/users" also matches to "/users/". *

The default value is {@code false}. */ public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) { this.useTrailingSlashMatch = useTrailingSlashMatch; if (getPatternParser() != null) { getPatternParser().setMatchOptionalTrailingSeparator(useTrailingSlashMatch); } } /** * Whether to match to URLs irrespective of the presence of a trailing slash. */ public boolean useTrailingSlashMatch() { return this.useTrailingSlashMatch; } /** * 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. *

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; } /** * Look up a handler for the URL path of the given request. * @param request current HTTP request * @return the handler instance, or {@code null} if none found */ @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request); Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { 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 (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } /** * Look up a handler instance for the given URL path. This method is used * when parsed {@code PathPattern}s are {@link #usesPathPatterns() enabled}. * @param path the parsed RequestPath * @param lookupPath the String lookupPath for checking direct hits * @param request current HTTP request * @return a matching handler, or {@code null} if not found * @since 5.3 */ @Nullable protected Object lookupHandler( RequestPath path, String lookupPath, HttpServletRequest request) throws Exception { Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List matches = null; for (PathPattern pattern : this.pathPatternHandlerMap.keySet()) { if (pattern.matches(path.pathWithinApplication())) { matches = (matches != null ? matches : new ArrayList<>()); matches.add(pattern); } } if (matches == null) { return null; } if (matches.size() > 1) { matches.sort(PathPattern.SPECIFICITY_COMPARATOR); if (logger.isTraceEnabled()) { logger.debug("Matching patterns " + matches); } } PathPattern pattern = matches.get(0); handler = this.pathPatternHandlerMap.get(pattern); if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); PathContainer pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication()); return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping.value(), null); } /** * Look up a handler instance for the given URL path. This method is used * when String pattern matching with {@code PathMatcher} is in use. * @param lookupPath the path to match patterns against * @param request current HTTP request * @return a matching handler, or {@code null} if not found * @see #exposePathWithinMapping * @see AntPathMatcher */ @Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isTraceEnabled() && matchingPatterns.size() > 1) { logger.trace("Matching patterns " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { logger.trace("URI variables " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } @Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; } /** * Validate the given handler against the current request. *

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 request) throws 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. *

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} if no variables found * @return the final handler object */ protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, @Nullable Map 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(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern); request.setAttribute(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 uriTemplateVariables, HttpServletRequest request) { request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); } @Override @Nullable public RequestMatchResult match(HttpServletRequest request, String pattern) { Assert.isNull(getPatternParser(), "This HandlerMapping uses PathPatterns."); String lookupPath = UrlPathHelper.getResolvedLookupPath(request); if (getPathMatcher().match(pattern, lookupPath)) { return new RequestMatchResult(pattern, lookupPath, getPathMatcher()); } else if (useTrailingSlashMatch()) { if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", lookupPath)) { return new RequestMatchResult(pattern + "/", lookupPath, getPathMatcher()); } } return null; } /** * 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 beanName) throws 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 handler) throws 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 = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.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.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } private String getHandlerDescription(Object handler) { return (handler instanceof String ? "'" + handler + "'" : handler.toString()); } /** * Return the handler mappings as a read-only Map, with the registered path * or pattern as key and the handler object (or handler bean name in case of * a lazy-init handler), as value. * @see #getDefaultHandler() */ public final Map getHandlerMap() { return Collections.unmodifiableMap(this.handlerMap); } /** * Identical to {@link #getHandlerMap()} but populated when parsed patterns * are {@link #usesPathPatterns() enabled}; otherwise empty. * @since 5.3 */ public final Map getPathPatternHandlerMap() { return (this.pathPatternHandlerMap.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(this.pathPatternHandlerMap)); } /** * Indicates whether this handler mapping support type-level mappings. Default to {@code false}. */ protected boolean supportsTypeLevelMappings() { return false; } /** * Special interceptor for exposing the * {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute. * @see AbstractUrlHandlerMapping#exposePathWithinMapping */ private class PathExposingHandlerInterceptor implements HandlerInterceptor { 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); request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler); request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings()); return true; } } /** * Special interceptor for exposing the * {@link AbstractUrlHandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE} attribute. * @see AbstractUrlHandlerMapping#exposePathWithinMapping */ private class UriTemplateVariablesHandlerInterceptor implements HandlerInterceptor { private final Map uriTemplateVariables; public UriTemplateVariablesHandlerInterceptor(Map uriTemplateVariables) { this.uriTemplateVariables = uriTemplateVariables; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { exposeUriTemplateVariables(this.uriTemplateVariables, request); return true; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy