Add PathPatternRegistry
This commit adds the new `PathPatternRegistry`, which holds a sorted set of `PathPattern`s and allows for searching/adding patterns This registry is being used in `HandlerMapping` implementations and separates path pattern parsing/matching logic from the rest. Directly using `PathPattern` instances should improve the performance of those `HandlerMapping` implementations, since the parsing and generation of pattern variants (trailing slash, suffix patterns, etc) is done only once. Issue: SPR-14544
This commit is contained in:
@@ -105,6 +105,7 @@ public class PathMatchConfigurer {
|
||||
return this.pathHelper;
|
||||
}
|
||||
|
||||
//TODO: remove
|
||||
protected PathMatcher getPathMatcher() {
|
||||
return this.pathMatcher;
|
||||
}
|
||||
|
||||
@@ -151,9 +151,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
if (configurer.isUseTrailingSlashMatch() != null) {
|
||||
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
|
||||
}
|
||||
if (configurer.getPathMatcher() != null) {
|
||||
mapping.setPathMatcher(configurer.getPathMatcher());
|
||||
}
|
||||
if (configurer.getPathHelper() != null) {
|
||||
mapping.setPathHelper(configurer.getPathHelper());
|
||||
}
|
||||
@@ -246,9 +243,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
|
||||
if (handlerMapping != null) {
|
||||
PathMatchConfigurer pathMatchConfigurer = getPathMatchConfigurer();
|
||||
if (pathMatchConfigurer.getPathMatcher() != null) {
|
||||
handlerMapping.setPathMatcher(pathMatchConfigurer.getPathMatcher());
|
||||
}
|
||||
if (pathMatchConfigurer.getPathHelper() != null) {
|
||||
handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper());
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.context.support.ApplicationObjectSupport;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.reactive.CorsProcessor;
|
||||
@@ -34,7 +33,8 @@ import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.web.reactive.HandlerMapping}
|
||||
@@ -53,12 +53,11 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||
|
||||
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
||||
|
||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
||||
|
||||
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
|
||||
|
||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||
|
||||
protected PathPatternRegistry patternRegistry = new PathPatternRegistry();
|
||||
|
||||
/**
|
||||
* Specify the order value for this HandlerMapping bean.
|
||||
@@ -102,22 +101,19 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PathMatcher implementation to use for matching URL paths
|
||||
* against registered URL patterns. Default is AntPathMatcher.
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
* Return the {@link PathPatternRegistry} instance to use for parsing
|
||||
* and matching path patterns.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
||||
this.pathMatcher = pathMatcher;
|
||||
this.globalCorsConfigSource.setPathMatcher(pathMatcher);
|
||||
public PathPatternRegistry getPatternRegistry() {
|
||||
return patternRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PathMatcher implementation to use for matching URL paths
|
||||
* against registered URL patterns.
|
||||
* Set the {@link PathPatternRegistry} instance to use for parsing
|
||||
* and matching path patterns.
|
||||
*/
|
||||
public PathMatcher getPathMatcher() {
|
||||
return this.pathMatcher;
|
||||
public void setPatternRegistry(PathPatternRegistry patternRegistry) {
|
||||
this.patternRegistry = patternRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,13 +122,16 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
|
||||
* configuration if any.
|
||||
*/
|
||||
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
||||
this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
|
||||
for(String patternString : corsConfigurations.keySet()) {
|
||||
this.globalCorsConfigSource
|
||||
.registerCorsConfiguration(patternString, corsConfigurations.get(patternString));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "global" CORS configuration.
|
||||
*/
|
||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
||||
public Map<PathPattern, CorsConfiguration> getCorsConfigurations() {
|
||||
return this.globalCorsConfigSource.getCorsConfigurations();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,32 +16,32 @@
|
||||
|
||||
package org.springframework.web.reactive.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.SortedSet;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Abstract base class for URL-mapped
|
||||
* {@link org.springframework.web.reactive.HandlerMapping} implementations.
|
||||
*
|
||||
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
|
||||
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
|
||||
* various path pattern matches, e.g. a registered "/t*" pattern matches
|
||||
* both "/test" and "/team", "/test/*" matches all paths under "/test",
|
||||
* "/test/**" matches all paths below "/test". For details, see the
|
||||
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
|
||||
* {@link PathPatternParser} 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.
|
||||
* <p>Will search all path patterns to find the most specific match for the
|
||||
* current request path. The most specific pattern is defined as the longest
|
||||
* path pattern with the fewest captured variables and wildcards.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
@@ -49,12 +49,9 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*/
|
||||
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
|
||||
private boolean useTrailingSlashMatch = false;
|
||||
|
||||
private boolean lazyInitHandlers = false;
|
||||
|
||||
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
|
||||
|
||||
private final Map<PathPattern, Object> handlerMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||
@@ -62,14 +59,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
|
||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||
this.patternRegistry.setUseTrailingSlashMatch(useTrailingSlashMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||
*/
|
||||
public boolean useTrailingSlashMatch() {
|
||||
return this.useTrailingSlashMatch;
|
||||
return this.patternRegistry.useTrailingSlashMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +88,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
* as key and the handler object (or handler bean name in case of a lazy-init handler)
|
||||
* as value.
|
||||
*/
|
||||
public final Map<String, Object> getHandlerMap() {
|
||||
public final Map<PathPattern, Object> getHandlerMap() {
|
||||
return Collections.unmodifiableMap(this.handlerMap);
|
||||
}
|
||||
|
||||
@@ -122,7 +119,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
*
|
||||
* <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.
|
||||
* both "/test" and "/team". For details, see the PathPattern class.
|
||||
*
|
||||
* <p>Looks for the most exact pattern, where most exact is defined as
|
||||
* the longest path pattern.
|
||||
@@ -133,54 +130,21 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
*/
|
||||
protected Object lookupHandler(String urlPath, ServerWebExchange exchange) throws Exception {
|
||||
// Direct match?
|
||||
Object handler = this.handlerMap.get(urlPath);
|
||||
if (handler != null) {
|
||||
return handleMatch(handler, urlPath, urlPath, exchange);
|
||||
}
|
||||
|
||||
// Pattern match?
|
||||
List<String> matches = new ArrayList<>();
|
||||
for (String pattern : this.handlerMap.keySet()) {
|
||||
if (getPathMatcher().match(pattern, urlPath)) {
|
||||
matches.add(pattern);
|
||||
}
|
||||
else if (useTrailingSlashMatch()) {
|
||||
if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", urlPath)) {
|
||||
matches.add(pattern +"/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String bestMatch = null;
|
||||
Comparator<String> comparator = getPathMatcher().getPatternComparator(urlPath);
|
||||
SortedSet<PathPattern> matches = this.patternRegistry.findMatches(urlPath);
|
||||
if (!matches.isEmpty()) {
|
||||
Collections.sort(matches, comparator);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Matching patterns for request [" + urlPath + "] are " + matches);
|
||||
}
|
||||
bestMatch = matches.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 + "]");
|
||||
}
|
||||
}
|
||||
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
|
||||
return handleMatch(handler, bestMatch, pathWithinMapping, exchange);
|
||||
PathPattern bestMatch = matches.first();
|
||||
String pathWithinMapping = bestMatch.extractPathWithinPattern(urlPath);
|
||||
return handleMatch(this.handlerMap.get(bestMatch), bestMatch, pathWithinMapping, exchange);
|
||||
}
|
||||
|
||||
// No handler found...
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object handleMatch(Object handler, String bestMatch, String pathWithinMapping,
|
||||
private Object handleMatch(Object handler, PathPattern bestMatch, String pathWithinMapping,
|
||||
ServerWebExchange exchange) throws Exception {
|
||||
|
||||
// Bean name or resolved handler?
|
||||
@@ -243,20 +207,22 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
resolvedHandler = getApplicationContext().getBean(handlerName);
|
||||
}
|
||||
}
|
||||
for (PathPattern newPattern : this.patternRegistry.register(urlPath)) {
|
||||
Object mappedHandler = this.handlerMap.get(newPattern);
|
||||
if (mappedHandler != null) {
|
||||
if (mappedHandler != resolvedHandler) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
|
||||
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.handlerMap.put(newPattern, resolvedHandler);
|
||||
|
||||
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 {
|
||||
this.handlerMap.put(urlPath, resolvedHandler);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -17,11 +17,10 @@
|
||||
package org.springframework.web.reactive.resource;
|
||||
|
||||
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.SortedSet;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -33,11 +32,11 @@ import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||
|
||||
/**
|
||||
* A central component to use to obtain the public URL path that clients should
|
||||
@@ -56,9 +55,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
|
||||
private HttpRequestPathHelper urlPathHelper = new HttpRequestPathHelper();
|
||||
|
||||
private PathMatcher pathMatcher = new ParsingPathMatcher();
|
||||
private final PathPatternRegistry patternRegistry = new PathPatternRegistry();
|
||||
|
||||
private final Map<String, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
|
||||
private final Map<PathPattern, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
|
||||
|
||||
private boolean autodetect = true;
|
||||
|
||||
@@ -79,29 +78,16 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
return this.urlPathHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@code PathMatcher} to use when comparing target lookup path
|
||||
* against resource mappings.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@code PathMatcher}.
|
||||
*/
|
||||
public PathMatcher getPathMatcher() {
|
||||
return this.pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually configure the resource mappings.
|
||||
* <p><strong>Note:</strong> by default resource mappings are auto-detected
|
||||
* from the Spring {@code ApplicationContext}. However if this property is
|
||||
* used, the auto-detection is turned off.
|
||||
*/
|
||||
public void setHandlerMap(Map<String, ResourceWebHandler> handlerMap) {
|
||||
public void setHandlerMap(Map<PathPattern, ResourceWebHandler> handlerMap) {
|
||||
if (handlerMap != null) {
|
||||
this.patternRegistry.clear();
|
||||
this.patternRegistry.addAll(handlerMap.keySet());
|
||||
this.handlerMap.clear();
|
||||
this.handlerMap.putAll(handlerMap);
|
||||
this.autodetect = false;
|
||||
@@ -112,7 +98,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
* Return the resource mappings, either manually configured or auto-detected
|
||||
* when the Spring {@code ApplicationContext} is refreshed.
|
||||
*/
|
||||
public Map<String, ResourceWebHandler> getHandlerMap() {
|
||||
public Map<PathPattern, ResourceWebHandler> getHandlerMap() {
|
||||
return this.handlerMap;
|
||||
}
|
||||
|
||||
@@ -127,6 +113,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (isAutodetect()) {
|
||||
this.patternRegistry.clear();
|
||||
this.handlerMap.clear();
|
||||
detectResourceHandlers(event.getApplicationContext());
|
||||
if (this.handlerMap.isEmpty() && logger.isDebugEnabled()) {
|
||||
@@ -147,7 +134,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
AnnotationAwareOrderComparator.sort(handlerMappings);
|
||||
|
||||
for (SimpleUrlHandlerMapping hm : handlerMappings) {
|
||||
for (String pattern : hm.getHandlerMap().keySet()) {
|
||||
for (PathPattern pattern : hm.getHandlerMap().keySet()) {
|
||||
Object handler = hm.getHandlerMap().get(pattern);
|
||||
if (handler instanceof ResourceWebHandler) {
|
||||
ResourceWebHandler resourceHandler = (ResourceWebHandler) handler;
|
||||
@@ -156,6 +143,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
"locations=" + resourceHandler.getLocations() + ", " +
|
||||
"resolvers=" + resourceHandler.getResourceResolvers());
|
||||
}
|
||||
this.patternRegistry.add(pattern);
|
||||
this.handlerMap.put(pattern, resourceHandler);
|
||||
}
|
||||
}
|
||||
@@ -218,26 +206,18 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
||||
}
|
||||
|
||||
List<String> matchingPatterns = new ArrayList<>();
|
||||
for (String pattern : this.handlerMap.keySet()) {
|
||||
if (getPathMatcher().match(pattern, lookupPath)) {
|
||||
matchingPatterns.add(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingPatterns.isEmpty()) {
|
||||
SortedSet<PathPattern> matches = this.patternRegistry.findMatches(lookupPath);
|
||||
if (matches.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
|
||||
Collections.sort(matchingPatterns, patternComparator);
|
||||
|
||||
return Flux.fromIterable(matchingPatterns)
|
||||
return Flux.fromIterable(matches)
|
||||
.concatMap(pattern -> {
|
||||
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
|
||||
String pathWithinMapping = pattern.extractPathWithinPattern(lookupPath);
|
||||
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invoking ResourceResolverChain for URL pattern \"" + pattern + "\"");
|
||||
logger.trace("Invoking ResourceResolverChain for URL pattern \""
|
||||
+ pattern.getPatternString() + "\"");
|
||||
}
|
||||
ResourceWebHandler handler = this.handlerMap.get(pattern);
|
||||
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -16,22 +16,21 @@
|
||||
|
||||
package org.springframework.web.reactive.result.condition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
import org.springframework.web.util.ParsingPathMatcher;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternRegistry;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition that matches a request
|
||||
@@ -42,26 +41,17 @@ import org.springframework.web.util.ParsingPathMatcher;
|
||||
*/
|
||||
public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
|
||||
|
||||
private final Set<String> patterns;
|
||||
private final PathPatternRegistry patternRegistry;
|
||||
|
||||
private final HttpRequestPathHelper pathHelper;
|
||||
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
private final boolean useSuffixPatternMatch;
|
||||
|
||||
private final boolean useTrailingSlashMatch;
|
||||
|
||||
private final Set<String> fileExtensions = new HashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given URL patterns.
|
||||
* Each pattern that is not empty and does not start with "/" is prepended with "/".
|
||||
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
||||
*/
|
||||
public PatternsRequestCondition(String... patterns) {
|
||||
this(asList(patterns), null, null, true, true, null);
|
||||
this(patterns, null, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,66 +59,58 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
|
||||
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
|
||||
* @param pathHelper to determine the lookup path for a request
|
||||
* @param pathMatcher for pattern path matching
|
||||
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
|
||||
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
|
||||
* @param extensions file extensions to consider for path matching
|
||||
*/
|
||||
public PatternsRequestCondition(String[] patterns, HttpRequestPathHelper pathHelper,
|
||||
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
|
||||
Set<String> extensions) {
|
||||
boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, Set<String> extensions) {
|
||||
|
||||
this(asList(patterns), pathHelper, pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch, extensions);
|
||||
this(createPatternSet(patterns, useSuffixPatternMatch, useTrailingSlashMatch, extensions),
|
||||
(pathHelper != null ? pathHelper : new HttpRequestPathHelper()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor accepting a collection of patterns.
|
||||
*/
|
||||
private PatternsRequestCondition(Collection<String> patterns, HttpRequestPathHelper pathHelper,
|
||||
PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch,
|
||||
Set<String> fileExtensions) {
|
||||
|
||||
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
|
||||
this.pathHelper = (pathHelper != null ? pathHelper : new HttpRequestPathHelper());
|
||||
this.pathMatcher = (pathMatcher != null ? pathMatcher : new ParsingPathMatcher());
|
||||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||
if (fileExtensions != null) {
|
||||
for (String fileExtension : fileExtensions) {
|
||||
if (fileExtension.charAt(0) != '.') {
|
||||
fileExtension = "." + fileExtension;
|
||||
}
|
||||
this.fileExtensions.add(fileExtension);
|
||||
}
|
||||
private static PathPatternRegistry createPatternSet(String[] patterns,boolean useSuffixPatternMatch,
|
||||
boolean useTrailingSlashMatch, Set<String> extensions) {
|
||||
Set<String> fixedFileExtensions = (extensions != null) ? extensions.stream()
|
||||
.map(ext -> (ext.charAt(0) != '.') ? "." + ext : ext)
|
||||
.collect(Collectors.toSet()) : Collections.emptySet();
|
||||
PathPatternRegistry patternSet = new PathPatternRegistry();
|
||||
patternSet.setUseSuffixPatternMatch(useSuffixPatternMatch);
|
||||
patternSet.setUseTrailingSlashMatch(useTrailingSlashMatch);
|
||||
patternSet.setFileExtensions(extensions);
|
||||
if(patterns != null) {
|
||||
Arrays.asList(patterns).stream()
|
||||
.map(prependLeadingSlash())
|
||||
.forEach(p -> patternSet.register(p));
|
||||
}
|
||||
return patternSet;
|
||||
}
|
||||
|
||||
|
||||
private static List<String> asList(String... patterns) {
|
||||
return (patterns != null ? Arrays.asList(patterns) : Collections.emptyList());
|
||||
}
|
||||
|
||||
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
|
||||
if (patterns == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> result = new LinkedHashSet<>(patterns.size());
|
||||
for (String pattern : patterns) {
|
||||
private static Function<String, String> prependLeadingSlash() {
|
||||
return pattern -> {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
pattern = "/" + pattern;
|
||||
return "/" + pattern;
|
||||
}
|
||||
result.add(pattern);
|
||||
}
|
||||
return result;
|
||||
else {
|
||||
return pattern;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Set<String> getPatterns() {
|
||||
return this.patterns;
|
||||
private PatternsRequestCondition(PathPatternRegistry patternRegistry, HttpRequestPathHelper pathHelper) {
|
||||
this.patternRegistry = patternRegistry;
|
||||
this.pathHelper = pathHelper;
|
||||
}
|
||||
|
||||
|
||||
public Set<PathPattern> getPatterns() {
|
||||
return this.patternRegistry.getPatterns();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<String> getContent() {
|
||||
return this.patterns;
|
||||
protected Collection<PathPattern> getContent() {
|
||||
return this.patternRegistry.getPatterns();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,25 +130,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||
*/
|
||||
@Override
|
||||
public PatternsRequestCondition combine(PatternsRequestCondition other) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
|
||||
for (String pattern1 : this.patterns) {
|
||||
for (String pattern2 : other.patterns) {
|
||||
result.add(this.pathMatcher.combine(pattern1, pattern2));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!this.patterns.isEmpty()) {
|
||||
result.addAll(this.patterns);
|
||||
}
|
||||
else if (!other.patterns.isEmpty()) {
|
||||
result.addAll(other.patterns);
|
||||
}
|
||||
else {
|
||||
result.add("");
|
||||
}
|
||||
return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
||||
this.useTrailingSlashMatch, this.fileExtensions);
|
||||
return new PatternsRequestCondition(this.patternRegistry.combine(other.patternRegistry), this.pathHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,16 +151,19 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||
*/
|
||||
@Override
|
||||
public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) {
|
||||
if (this.patterns.isEmpty()) {
|
||||
if (this.patternRegistry.getPatterns().isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
||||
List<String> matches = getMatchingPatterns(lookupPath);
|
||||
SortedSet<PathPattern> matches = getMatchingPatterns(lookupPath);
|
||||
|
||||
return matches.isEmpty() ? null :
|
||||
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
||||
this.useTrailingSlashMatch, this.fileExtensions);
|
||||
if(!matches.isEmpty()) {
|
||||
PathPatternRegistry registry = new PathPatternRegistry();
|
||||
registry.addAll(matches);
|
||||
return new PatternsRequestCondition(registry, this.pathHelper);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,54 +173,16 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||
* This method is provided as an alternative to be used if no request is available
|
||||
* (e.g. introspection, tooling, etc).
|
||||
* @param lookupPath the lookup path to match to existing patterns
|
||||
* @return a collection of matching patterns sorted with the closest match at the top
|
||||
* @return a sorted set of matching patterns sorted with the closest match first
|
||||
*/
|
||||
public List<String> getMatchingPatterns(String lookupPath) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
for (String pattern : this.patterns) {
|
||||
String match = getMatchingPattern(pattern, lookupPath);
|
||||
if (match != null) {
|
||||
matches.add(match);
|
||||
}
|
||||
}
|
||||
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
|
||||
return matches;
|
||||
}
|
||||
|
||||
private String getMatchingPattern(String pattern, String lookupPath) {
|
||||
if (pattern.equals(lookupPath)) {
|
||||
return pattern;
|
||||
}
|
||||
if (this.useSuffixPatternMatch) {
|
||||
if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
|
||||
for (String extension : this.fileExtensions) {
|
||||
if (this.pathMatcher.match(pattern + extension, lookupPath)) {
|
||||
return pattern + extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
boolean hasSuffix = pattern.indexOf('.') != -1;
|
||||
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
|
||||
return pattern + ".*";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.pathMatcher.match(pattern, lookupPath)) {
|
||||
return pattern;
|
||||
}
|
||||
if (this.useTrailingSlashMatch) {
|
||||
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
|
||||
return pattern +"/";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public SortedSet<PathPattern> getMatchingPatterns(String lookupPath) {
|
||||
return this.patternRegistry.findMatches(lookupPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the two conditions based on the URL patterns they contain.
|
||||
* Patterns are compared one at a time, from top to bottom via
|
||||
* {@link PathMatcher#getPatternComparator(String)}. If all compared
|
||||
* {@link PathPatternRegistry#getComparator(String)}. If all compared
|
||||
* patterns match equally, but one instance has more patterns, it is
|
||||
* considered a closer match.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
@@ -264,24 +193,8 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||
@Override
|
||||
public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) {
|
||||
String lookupPath = this.pathHelper.getLookupPathForRequest(exchange);
|
||||
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
|
||||
Iterator<String> iterator = this.patterns.iterator();
|
||||
Iterator<String> iteratorOther = other.patterns.iterator();
|
||||
while (iterator.hasNext() && iteratorOther.hasNext()) {
|
||||
int result = patternComparator.compare(iterator.next(), iteratorOther.next());
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (iterator.hasNext()) {
|
||||
return -1;
|
||||
}
|
||||
else if (iteratorOther.hasNext()) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
Comparator<PathPatternRegistry> comparator = this.patternRegistry.getComparator(lookupPath);
|
||||
return comparator.compare(this.patternRegistry, other.patternRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -44,6 +45,7 @@ import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link HandlerMapping} implementations that define
|
||||
@@ -88,7 +90,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
||||
}
|
||||
|
||||
private final MultiValueMap<PathPattern, T> mappingLookup = new LinkedMultiValueMap<>();
|
||||
|
||||
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
||||
|
||||
private final MappingRegistry mappingRegistry = new MappingRegistry();
|
||||
|
||||
|
||||
@@ -98,12 +103,12 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
* Return a (read-only) map with all mappings and HandlerMethod's.
|
||||
*/
|
||||
public Map<T, HandlerMethod> getHandlerMethods() {
|
||||
this.mappingRegistry.acquireReadLock();
|
||||
this.readWriteLock.readLock().lock();
|
||||
try {
|
||||
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
|
||||
}
|
||||
finally {
|
||||
this.mappingRegistry.releaseReadLock();
|
||||
this.readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +127,18 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
* @param method the method
|
||||
*/
|
||||
public void registerMapping(T mapping, Object handler, Method method) {
|
||||
this.mappingRegistry.register(mapping, handler, method);
|
||||
this.readWriteLock.writeLock().lock();
|
||||
try {
|
||||
Set<PathPattern> patterns = getMappingPathPatterns(mapping);
|
||||
getPatternRegistry().addAll(patterns);
|
||||
patterns.forEach(pathPattern -> {
|
||||
this.mappingLookup.add(pathPattern, mapping);
|
||||
});
|
||||
this.mappingRegistry.register(mapping, handler, method);
|
||||
}
|
||||
finally {
|
||||
this.readWriteLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +147,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
* @param mapping the mapping to unregister
|
||||
*/
|
||||
public void unregisterMapping(T mapping) {
|
||||
this.mappingRegistry.unregister(mapping);
|
||||
this.readWriteLock.writeLock().lock();
|
||||
try {
|
||||
getMappingPathPatterns(mapping)
|
||||
.forEach(pathPattern -> getPatternRegistry().remove(pathPattern));
|
||||
this.mappingRegistry.unregister(mapping);
|
||||
}
|
||||
finally {
|
||||
this.readWriteLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,23 +219,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
for (Map.Entry<Method, T> entry : methods.entrySet()) {
|
||||
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
|
||||
T mapping = entry.getValue();
|
||||
registerHandlerMethod(handler, invocableMethod, mapping);
|
||||
registerMapping(mapping, handler, invocableMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler method and its unique mapping. Invoked at startup for
|
||||
* each detected handler method.
|
||||
* @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) {
|
||||
this.mappingRegistry.register(mapping, handler, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the HandlerMethod instance.
|
||||
* @param handler either a bean name or an actual handler instance
|
||||
@@ -258,36 +269,30 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Looking up handler method for path " + lookupPath);
|
||||
}
|
||||
this.mappingRegistry.acquireReadLock();
|
||||
|
||||
try {
|
||||
// Ensure form data is parsed for "params" conditions...
|
||||
return exchange.getRequestParams()
|
||||
.then(() -> {
|
||||
HandlerMethod handlerMethod = null;
|
||||
try {
|
||||
handlerMethod = lookupHandlerMethod(lookupPath, exchange);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (handlerMethod != null) {
|
||||
logger.debug("Returning handler method [" + handlerMethod + "]");
|
||||
}
|
||||
else {
|
||||
logger.debug("Did not find handler method for [" + lookupPath + "]");
|
||||
}
|
||||
}
|
||||
// Ensure form data is parsed for "params" conditions...
|
||||
return exchange.getRequestParams()
|
||||
.then(() -> {
|
||||
HandlerMethod handlerMethod = null;
|
||||
try {
|
||||
handlerMethod = lookupHandlerMethod(lookupPath, exchange);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (handlerMethod != null) {
|
||||
handlerMethod = handlerMethod.createWithResolvedBean();
|
||||
logger.debug("Returning handler method [" + handlerMethod + "]");
|
||||
}
|
||||
return Mono.justOrEmpty(handlerMethod);
|
||||
});
|
||||
}
|
||||
finally {
|
||||
this.mappingRegistry.releaseReadLock();
|
||||
}
|
||||
else {
|
||||
logger.debug("Did not find handler method for [" + lookupPath + "]");
|
||||
}
|
||||
}
|
||||
if (handlerMethod != null) {
|
||||
handlerMethod = handlerMethod.createWithResolvedBean();
|
||||
}
|
||||
return Mono.justOrEmpty(handlerMethod);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,25 +301,17 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
* @param lookupPath mapping lookup path within the current servlet mapping
|
||||
* @param exchange the current exchange
|
||||
* @return the best-matching handler method, or {@code null} if no match
|
||||
* @see #handleMatch(Object, String, ServerWebExchange)
|
||||
* @see #handleMatch(Object, String, ServerWebExchange)
|
||||
* @see #handleNoMatch(Set, String, ServerWebExchange)
|
||||
*/
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, ServerWebExchange exchange)
|
||||
throws Exception {
|
||||
|
||||
List<Match> matches = new ArrayList<Match>();
|
||||
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
|
||||
if (directPathMatches != null) {
|
||||
addMatchingMappings(directPathMatches, matches, exchange);
|
||||
}
|
||||
if (matches.isEmpty()) {
|
||||
// No choice but to go through all mappings...
|
||||
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);
|
||||
}
|
||||
List<Match> matches = findMatchingMappings(lookupPath, exchange);
|
||||
|
||||
if (!matches.isEmpty()) {
|
||||
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
|
||||
Collections.sort(matches, comparator);
|
||||
matches.sort(comparator);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
|
||||
lookupPath + "] : " + matches);
|
||||
@@ -340,13 +337,27 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
}
|
||||
}
|
||||
|
||||
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, ServerWebExchange exchange) {
|
||||
for (T mapping : mappings) {
|
||||
T match = getMatchingMapping(mapping, exchange);
|
||||
if (match != null) {
|
||||
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
|
||||
private List<Match> findMatchingMappings(String lookupPath, ServerWebExchange exchange) {
|
||||
List<Match> matches = new ArrayList<>();
|
||||
try {
|
||||
this.readWriteLock.readLock().lock();
|
||||
// Fast lookup of potential matching mappings given the current lookup path
|
||||
Set<PathPattern> matchingPatterns = getPatternRegistry().findMatches(lookupPath);
|
||||
Set<T> mappings = matchingPatterns.stream()
|
||||
.flatMap(pattern -> this.mappingLookup.get(pattern).stream())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (T mapping : mappings) {
|
||||
T match = getMatchingMapping(mapping, exchange);
|
||||
if (match != null) {
|
||||
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.readWriteLock.readLock().unlock();
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,7 +420,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
/**
|
||||
* Extract and return the URL paths contained in a mapping.
|
||||
*/
|
||||
protected abstract Set<String> getMappingPathPatterns(T mapping);
|
||||
protected abstract Set<PathPattern> getMappingPathPatterns(T mapping);
|
||||
|
||||
/**
|
||||
* Check if a mapping matches the current request and return a (potentially
|
||||
@@ -431,7 +442,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
|
||||
/**
|
||||
* A registry that maintains all mappings to handler methods, exposing methods
|
||||
* to perform lookups and providing concurrent access.
|
||||
* to perform lookups.
|
||||
*
|
||||
* <p>Package-private for testing purposes.
|
||||
*/
|
||||
@@ -439,28 +450,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
|
||||
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
|
||||
|
||||
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
|
||||
|
||||
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
|
||||
private final Map<T, HandlerMethod> handlerMethodLookup = new LinkedHashMap<>();
|
||||
|
||||
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
|
||||
|
||||
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
||||
|
||||
/**
|
||||
* Return all mappings and handler methods. Not thread-safe.
|
||||
* @see #acquireReadLock()
|
||||
*/
|
||||
public Map<T, HandlerMethod> getMappings() {
|
||||
return this.mappingLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return matches for the given URL path. Not thread-safe.
|
||||
* @see #acquireReadLock()
|
||||
*/
|
||||
public List<T> getMappingsByUrl(String urlPath) {
|
||||
return this.urlLookup.get(urlPath);
|
||||
return this.handlerMethodLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,92 +469,42 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
return this.corsLookup.get(original != null ? original : handlerMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the read lock when using getMappings and getMappingsByUrl.
|
||||
*/
|
||||
public void acquireReadLock() {
|
||||
this.readWriteLock.readLock().lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the read lock after using getMappings and getMappingsByUrl.
|
||||
*/
|
||||
public void releaseReadLock() {
|
||||
this.readWriteLock.readLock().unlock();
|
||||
}
|
||||
|
||||
public void register(T mapping, Object handler, Method method) {
|
||||
this.readWriteLock.writeLock().lock();
|
||||
try {
|
||||
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
||||
assertUniqueMethodMapping(handlerMethod, mapping);
|
||||
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
||||
assertUniqueMethodMapping(handlerMethod, mapping);
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
|
||||
}
|
||||
this.mappingLookup.put(mapping, handlerMethod);
|
||||
|
||||
List<String> directUrls = getDirectUrls(mapping);
|
||||
for (String url : directUrls) {
|
||||
this.urlLookup.add(url, mapping);
|
||||
}
|
||||
|
||||
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
||||
if (corsConfig != null) {
|
||||
this.corsLookup.put(handlerMethod, corsConfig);
|
||||
}
|
||||
|
||||
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls));
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
|
||||
}
|
||||
finally {
|
||||
this.readWriteLock.writeLock().unlock();
|
||||
this.handlerMethodLookup.put(mapping, handlerMethod);
|
||||
|
||||
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
||||
if (corsConfig != null) {
|
||||
this.corsLookup.put(handlerMethod, corsConfig);
|
||||
}
|
||||
|
||||
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));
|
||||
}
|
||||
|
||||
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
|
||||
HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
|
||||
HandlerMethod handlerMethod = this.handlerMethodLookup.get(mapping);
|
||||
if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
|
||||
newHandlerMethod + "\nto " + mapping + ": There is already '" +
|
||||
handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
|
||||
"Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
|
||||
newHandlerMethod + "\nto " + mapping + ": There is already '" +
|
||||
handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getDirectUrls(T mapping) {
|
||||
List<String> urls = new ArrayList<>(1);
|
||||
for (String path : getMappingPathPatterns(mapping)) {
|
||||
if (!getPathMatcher().isPattern(path)) {
|
||||
urls.add(path);
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void unregister(T mapping) {
|
||||
this.readWriteLock.writeLock().lock();
|
||||
try {
|
||||
MappingRegistration<T> definition = this.registry.remove(mapping);
|
||||
if (definition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mappingLookup.remove(definition.getMapping());
|
||||
|
||||
for (String url : definition.getDirectUrls()) {
|
||||
List<T> list = this.urlLookup.get(url);
|
||||
if (list != null) {
|
||||
list.remove(definition.getMapping());
|
||||
if (list.isEmpty()) {
|
||||
this.urlLookup.remove(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.corsLookup.remove(definition.getHandlerMethod());
|
||||
}
|
||||
finally {
|
||||
this.readWriteLock.writeLock().unlock();
|
||||
MappingRegistration<T> definition = this.registry.remove(mapping);
|
||||
if (definition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlerMethodLookup.remove(definition.getMapping());
|
||||
|
||||
this.corsLookup.remove(definition.getHandlerMethod());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,14 +515,11 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
|
||||
private final HandlerMethod handlerMethod;
|
||||
|
||||
private final List<String> directUrls;
|
||||
|
||||
public MappingRegistration(T mapping, HandlerMethod handlerMethod, List<String> directUrls) {
|
||||
public MappingRegistration(T mapping, HandlerMethod handlerMethod) {
|
||||
Assert.notNull(mapping, "Mapping must not be null");
|
||||
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
|
||||
this.mapping = mapping;
|
||||
this.handlerMethod = handlerMethod;
|
||||
this.directUrls = (directUrls != null ? directUrls : Collections.emptyList());
|
||||
}
|
||||
|
||||
public T getMapping() {
|
||||
@@ -584,10 +529,6 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
public HandlerMethod getHandlerMethod() {
|
||||
return this.handlerMethod;
|
||||
}
|
||||
|
||||
public List<String> getDirectUrls() {
|
||||
return this.directUrls;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
|
||||
@@ -472,7 +471,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
RequestedContentTypeResolver contentTypeResolver = this.options.getContentTypeResolver();
|
||||
|
||||
PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
|
||||
this.paths, this.options.getPathHelper(), this.options.getPathMatcher(),
|
||||
this.paths, this.options.getPathHelper(),
|
||||
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
|
||||
this.options.getFileExtensions());
|
||||
|
||||
@@ -497,11 +496,9 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
|
||||
private HttpRequestPathHelper pathHelper;
|
||||
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
private boolean trailingSlashMatch = true;
|
||||
|
||||
private boolean suffixPatternMatch = true;
|
||||
private boolean suffixPatternMatch = false;
|
||||
|
||||
private boolean registeredSuffixPatternMatch = false;
|
||||
|
||||
@@ -519,18 +516,6 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
return this.pathHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom PathMatcher to use for the PatternsRequestCondition.
|
||||
* <p>By default this is not set.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
public PathMatcher getPathMatcher() {
|
||||
return this.pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to apply trailing slash matching in PatternsRequestCondition.
|
||||
* <p>By default this is set to 'true'.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -45,6 +46,7 @@ import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
|
||||
/**
|
||||
* Abstract base class for classes for which {@link RequestMappingInfo} defines
|
||||
@@ -72,7 +74,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||
* Get the URL path patterns associated with this {@link RequestMappingInfo}.
|
||||
*/
|
||||
@Override
|
||||
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
|
||||
protected Set<PathPattern> getMappingPathPatterns(RequestMappingInfo info) {
|
||||
return info.getPatternsCondition().getPatterns();
|
||||
}
|
||||
|
||||
@@ -105,23 +107,22 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||
protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) {
|
||||
super.handleMatch(info, lookupPath, exchange);
|
||||
|
||||
String bestPattern;
|
||||
Map<String, String> uriVariables;
|
||||
Map<String, String> decodedUriVariables;
|
||||
|
||||
Set<String> patterns = info.getPatternsCondition().getPatterns();
|
||||
SortedSet<PathPattern> patterns = info.getPatternsCondition().getMatchingPatterns(lookupPath);
|
||||
if (patterns.isEmpty()) {
|
||||
bestPattern = lookupPath;
|
||||
uriVariables = Collections.emptyMap();
|
||||
decodedUriVariables = Collections.emptyMap();
|
||||
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE,
|
||||
getPatternRegistry().parsePattern(lookupPath));
|
||||
}
|
||||
else {
|
||||
bestPattern = patterns.iterator().next();
|
||||
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
|
||||
PathPattern bestPattern = patterns.first();
|
||||
uriVariables = bestPattern.matchAndExtract(lookupPath);
|
||||
decodedUriVariables = getPathHelper().decodePathVariables(exchange, uriVariables);
|
||||
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
|
||||
}
|
||||
|
||||
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
|
||||
exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
|
||||
|
||||
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(exchange, uriVariables);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -48,9 +48,9 @@ import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerM
|
||||
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||
implements EmbeddedValueResolverAware {
|
||||
|
||||
private boolean useSuffixPatternMatch = true;
|
||||
private boolean useSuffixPatternMatch = false;
|
||||
|
||||
private boolean useRegisteredSuffixPatternMatch = true;
|
||||
private boolean useRegisteredSuffixPatternMatch = false;
|
||||
|
||||
private boolean useTrailingSlashMatch = true;
|
||||
|
||||
@@ -113,7 +113,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
public void afterPropertiesSet() {
|
||||
this.config = new RequestMappingInfo.BuilderConfiguration();
|
||||
this.config.setPathHelper(getPathHelper());
|
||||
this.config.setPathMatcher(getPathMatcher());
|
||||
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
|
||||
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
|
||||
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
|
||||
|
||||
Reference in New Issue
Block a user