Introduce HandlerMapping introspection API
Issue: SPR-14321
This commit is contained in:
@@ -27,21 +27,21 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.cors.CorsProcessor;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
import org.springframework.web.context.support.WebApplicationObjectSupport;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.CorsProcessor;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.cors.DefaultCorsProcessor;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.cors.DefaultCorsProcessor;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
@@ -471,7 +471,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||
}
|
||||
|
||||
|
||||
private class PreFlightHandler implements HttpRequestHandler {
|
||||
private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
|
||||
|
||||
private final CorsConfiguration config;
|
||||
|
||||
@@ -485,10 +485,15 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||
|
||||
corsProcessor.processRequest(this.config, request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class CorsInterceptor extends HandlerInterceptorAdapter {
|
||||
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
|
||||
|
||||
private final CorsConfiguration config;
|
||||
|
||||
@@ -502,6 +507,11 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||
|
||||
return corsProcessor.processRequest(this.config, request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.support.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.support.RequestMatchResult;
|
||||
|
||||
/**
|
||||
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
|
||||
@@ -50,7 +52,8 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||
* @author Arjen Poutsma
|
||||
* @since 16.04.2003
|
||||
*/
|
||||
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping
|
||||
implements MatchableHandlerMapping {
|
||||
|
||||
private Object rootHandler;
|
||||
|
||||
@@ -279,6 +282,20 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
||||
String lookupPath = getUrlPathHelper().getLookupPathForRequest(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
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
@@ -38,6 +40,8 @@ import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.RequestCondition;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.servlet.support.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.support.RequestMatchResult;
|
||||
|
||||
/**
|
||||
* Creates {@link RequestMappingInfo} instances from type and method-level
|
||||
@@ -50,7 +54,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
|
||||
* @since 3.1
|
||||
*/
|
||||
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||
implements EmbeddedValueResolverAware {
|
||||
implements EmbeddedValueResolverAware, MatchableHandlerMapping {
|
||||
|
||||
private boolean useSuffixPatternMatch = true;
|
||||
|
||||
@@ -274,6 +278,18 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
||||
RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
|
||||
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
|
||||
if (matchingInfo == null) {
|
||||
return null;
|
||||
}
|
||||
Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
|
||||
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
|
||||
return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
|
||||
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Helper class to get information from the {@code HandlerMapping} that would
|
||||
* serve a specific request.
|
||||
*
|
||||
* <p>Provides the following methods:
|
||||
* <ul>
|
||||
* <li>{@link #getMatchableHandlerMapping} -- obtain a {@code HandlerMapping}
|
||||
* to check request-matching criteria against.
|
||||
* <li>{@link #getCorsConfiguration} -- obtain the CORS configuration for the
|
||||
* request.
|
||||
* </ul>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.3
|
||||
*/
|
||||
public class HandlerMappingIntrospector implements CorsConfigurationSource {
|
||||
|
||||
private final List<HandlerMapping> handlerMappings;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor that detects the configured {@code HandlerMapping}s in the
|
||||
* given {@code ApplicationContext} or falling back on
|
||||
* "DispatcherServlet.properties" like the {@code DispatcherServlet}.
|
||||
*/
|
||||
public HandlerMappingIntrospector(ApplicationContext context) {
|
||||
this.handlerMappings = initHandlerMappings(context);
|
||||
}
|
||||
|
||||
|
||||
private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
|
||||
|
||||
Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
context, HandlerMapping.class, true, false);
|
||||
|
||||
if (!beans.isEmpty()) {
|
||||
List<HandlerMapping> mappings = new ArrayList<HandlerMapping>(beans.values());
|
||||
AnnotationAwareOrderComparator.sort(mappings);
|
||||
return mappings;
|
||||
}
|
||||
|
||||
return initDefaultHandlerMappings(context);
|
||||
}
|
||||
|
||||
private static List<HandlerMapping> initDefaultHandlerMappings(ApplicationContext context) {
|
||||
Properties props;
|
||||
String path = "DispatcherServlet.properties";
|
||||
try {
|
||||
Resource resource = new ClassPathResource(path, DispatcherServlet.class);
|
||||
props = PropertiesLoaderUtils.loadProperties(resource);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
|
||||
}
|
||||
|
||||
String value = props.getProperty(HandlerMapping.class.getName());
|
||||
String[] names = StringUtils.commaDelimitedListToStringArray(value);
|
||||
List<HandlerMapping> result = new ArrayList<HandlerMapping>(names.length);
|
||||
for (String name : names) {
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
|
||||
Object mapping = context.getAutowireCapableBeanFactory().createBean(clazz);
|
||||
result.add((HandlerMapping) mapping);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured HandlerMapping's.
|
||||
*/
|
||||
public List<HandlerMapping> getHandlerMappings() {
|
||||
return this.handlerMappings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the {@link HandlerMapping} that would handle the given request and
|
||||
* return it as a {@link MatchableHandlerMapping} that can be used to
|
||||
* test request-matching criteria. If the matching HandlerMapping is not an
|
||||
* instance of {@link MatchableHandlerMapping}, an IllegalStateException is
|
||||
* raised.
|
||||
*
|
||||
* @param request the current request
|
||||
* @return the resolved matcher, or {@code null}
|
||||
* @throws Exception if any of the HandlerMapping's raise an exception
|
||||
*/
|
||||
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
|
||||
HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
|
||||
for (HandlerMapping handlerMapping : this.handlerMappings) {
|
||||
Object handler = handlerMapping.getHandler(wrapper);
|
||||
if (handler == null) {
|
||||
continue;
|
||||
}
|
||||
if (handlerMapping instanceof MatchableHandlerMapping) {
|
||||
return ((MatchableHandlerMapping) handlerMapping);
|
||||
}
|
||||
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
|
||||
for (HandlerMapping handlerMapping : this.handlerMappings) {
|
||||
HandlerExecutionChain handler = null;
|
||||
try {
|
||||
handler = handlerMapping.getHandler(wrapper);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
if (handler == null) {
|
||||
continue;
|
||||
}
|
||||
if (handler.getInterceptors() != null) {
|
||||
for (HandlerInterceptor interceptor : handler.getInterceptors()) {
|
||||
if (interceptor instanceof CorsConfigurationSource) {
|
||||
return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler.getHandler() instanceof CorsConfigurationSource) {
|
||||
return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Request wrapper that ignores request attribute changes.
|
||||
*/
|
||||
private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
|
||||
private RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
// Ignore attribute change
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.support;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Additional interface that a {@link HandlerMapping} can implement to expose
|
||||
* a request matching API aligned with its internal request matching
|
||||
* configuration and implementation.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.3
|
||||
* @see HandlerMappingIntrospector
|
||||
*/
|
||||
public interface MatchableHandlerMapping {
|
||||
|
||||
/**
|
||||
* Whether the given request matches the request criteria.
|
||||
* @param request the current request
|
||||
* @param pattern the pattern to match
|
||||
* @return the result from request matching or {@code null}
|
||||
*/
|
||||
RequestMatchResult match(HttpServletRequest request, String pattern);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
/**
|
||||
* Container for the result from request pattern matching via
|
||||
* {@link MatchableHandlerMapping} with a method to further extract URI template
|
||||
* variables from the pattern.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.3
|
||||
*/
|
||||
public class RequestMatchResult {
|
||||
|
||||
private final String matchingPattern;
|
||||
|
||||
private final String lookupPath;
|
||||
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance with a matching pattern.
|
||||
* @param matchingPattern the matching pattern, possibly not the same as the
|
||||
* input pattern, e.g. inputPattern="/foo" and matchingPattern="/foo/".
|
||||
* @param lookupPath the lookup path extracted from the request
|
||||
* @param pathMatcher the PathMatcher used
|
||||
*/
|
||||
public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
|
||||
Assert.hasText(matchingPattern, "'matchingPattern' is required");
|
||||
Assert.hasText(lookupPath, "'lookupPath' is required");
|
||||
Assert.notNull(pathMatcher, "'pathMatcher' is required");
|
||||
this.matchingPattern = matchingPattern;
|
||||
this.lookupPath = lookupPath;
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether the pattern was matched to the request.
|
||||
*/
|
||||
public boolean isMatch() {
|
||||
return (this.matchingPattern != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract URI template variables from the matching pattern as defined in
|
||||
* {@link PathMatcher#extractUriTemplateVariables}.
|
||||
* @return a map with URI template variables
|
||||
*/
|
||||
public Map<String, String> extractUriTemplateVariables() {
|
||||
if (!isMatch()) {
|
||||
return Collections.<String, String>emptyMap();
|
||||
}
|
||||
return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user