SPR-8898 Allow match by trailing slash in RequestMappingHandlerMapping.
This commit is contained in:
parent
569426dfdf
commit
6f150e4f07
|
@ -19,6 +19,7 @@ Changes in version 3.1 GA (2011-12-12)
|
|||
* ResourceHttpRequestHandler and ContentNegotiatingViewResolver use consistent mime type resolution
|
||||
* Portlet MVC annotation mapping allows for distributing action names across controllers
|
||||
* added String constants to MediaType
|
||||
* add useTrailingSlashMatch property to RequestMappingHandlerMapping
|
||||
|
||||
|
||||
Changes in version 3.1 RC2 (2011-11-28)
|
||||
|
|
|
@ -50,13 +50,15 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
|
||||
private final boolean useSuffixPatternMatch;
|
||||
|
||||
private final boolean useTrailingSlashMatch;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given URL patterns.
|
||||
* Each pattern that is not empty and does not start with "/" is pre-pended 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);
|
||||
this(asList(patterns), null, null, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,12 +68,14 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
|
||||
* @param pathMatcher a {@link PathMatcher} for pattern path matching
|
||||
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
|
||||
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
|
||||
*/
|
||||
public PatternsRequestCondition(String[] patterns,
|
||||
UrlPathHelper urlPathHelper,
|
||||
PathMatcher pathMatcher,
|
||||
boolean useSuffixPatternMatch) {
|
||||
this(asList(patterns), urlPathHelper, pathMatcher, useSuffixPatternMatch);
|
||||
boolean useSuffixPatternMatch,
|
||||
boolean useTrailingSlashMatch) {
|
||||
this(asList(patterns), urlPathHelper, pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,11 +84,13 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
private PatternsRequestCondition(Collection<String> patterns,
|
||||
UrlPathHelper urlPathHelper,
|
||||
PathMatcher pathMatcher,
|
||||
boolean useSuffixPatternMatch) {
|
||||
boolean useSuffixPatternMatch,
|
||||
boolean useTrailingSlashMatch) {
|
||||
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
|
||||
this.urlPathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper();
|
||||
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
|
||||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||
}
|
||||
|
||||
private static List<String> asList(String... patterns) {
|
||||
|
@ -106,12 +112,12 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
}
|
||||
|
||||
public Set<String> getPatterns() {
|
||||
return patterns;
|
||||
return this.patterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<String> getContent() {
|
||||
return patterns;
|
||||
return this.patterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -134,7 +140,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
|
||||
for (String pattern1 : this.patterns) {
|
||||
for (String pattern2 : other.patterns) {
|
||||
result.add(pathMatcher.combine(pattern1, pattern2));
|
||||
result.add(this.pathMatcher.combine(pattern1, pattern2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +153,8 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
else {
|
||||
result.add("");
|
||||
}
|
||||
return new PatternsRequestCondition(result, urlPathHelper, pathMatcher, useSuffixPatternMatch);
|
||||
return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
||||
this.useTrailingSlashMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,10 +177,10 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
* or {@code null} if no patterns match.
|
||||
*/
|
||||
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
if (patterns.isEmpty()) {
|
||||
if (this.patterns.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
|
||||
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
|
||||
List<String> matches = new ArrayList<String>();
|
||||
for (String pattern : patterns) {
|
||||
String match = getMatchingPattern(pattern, lookupPath);
|
||||
|
@ -181,27 +188,30 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
matches.add(match);
|
||||
}
|
||||
}
|
||||
Collections.sort(matches, pathMatcher.getPatternComparator(lookupPath));
|
||||
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
|
||||
return matches.isEmpty() ? null :
|
||||
new PatternsRequestCondition(matches, urlPathHelper, pathMatcher, useSuffixPatternMatch);
|
||||
new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
|
||||
this.useTrailingSlashMatch);
|
||||
}
|
||||
|
||||
private String getMatchingPattern(String pattern, String lookupPath) {
|
||||
if (pattern.equals(lookupPath)) {
|
||||
return pattern;
|
||||
}
|
||||
if (useSuffixPatternMatch) {
|
||||
if (this.useSuffixPatternMatch) {
|
||||
boolean hasSuffix = pattern.indexOf('.') != -1;
|
||||
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
|
||||
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
|
||||
return pattern + ".*";
|
||||
}
|
||||
}
|
||||
if (pathMatcher.match(pattern, lookupPath)) {
|
||||
if (this.pathMatcher.match(pattern, lookupPath)) {
|
||||
return pattern;
|
||||
}
|
||||
boolean endsWithSlash = pattern.endsWith("/");
|
||||
if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
|
||||
return pattern +"/";
|
||||
if (this.useTrailingSlashMatch) {
|
||||
if (!endsWithSlash && this.pathMatcher.match(pattern + "/", lookupPath)) {
|
||||
return pattern +"/";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -219,8 +229,8 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
* the best matches on top.
|
||||
*/
|
||||
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
|
||||
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
|
||||
Comparator<String> patternComparator = pathMatcher.getPatternComparator(lookupPath);
|
||||
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
|
||||
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
|
||||
|
||||
Iterator<String> iterator = patterns.iterator();
|
||||
Iterator<String> iteratorOther = other.patterns.iterator();
|
||||
|
|
|
@ -44,14 +44,25 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
|
||||
private boolean useSuffixPatternMatch = true;
|
||||
|
||||
private boolean useTrailingSlashMatch = true;
|
||||
|
||||
/**
|
||||
* Whether to use suffix pattern match (".*") when matching patterns to
|
||||
* requests. If enabled a method mapped to "/users" also matches to
|
||||
* "/users.*". The default value is "true".
|
||||
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
|
||||
* <p>The default value is {@code true}.
|
||||
*/
|
||||
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
|
||||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||
* If enabled a method mapped to "/users" also matches to "/users/".
|
||||
* <p>The default value is {@code true}.
|
||||
*/
|
||||
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
|
||||
this.useTrailingSlashMatch = useTrailingSlashMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use suffix pattern matching.
|
||||
|
@ -59,6 +70,12 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
public boolean useSuffixPatternMatch() {
|
||||
return this.useSuffixPatternMatch;
|
||||
}
|
||||
/**
|
||||
* Whether to match to URLs irrespective of the presence of a trailing slash.
|
||||
*/
|
||||
public boolean useTrailingSlashMatch() {
|
||||
return this.useTrailingSlashMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
@ -125,7 +142,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
|
||||
return new RequestMappingInfo(
|
||||
new PatternsRequestCondition(annotation.value(),
|
||||
getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch),
|
||||
getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch),
|
||||
new RequestMethodsRequestCondition(annotation.method()),
|
||||
new ParamsRequestCondition(annotation.params()),
|
||||
new HeadersRequestCondition(annotation.headers()),
|
||||
|
|
|
@ -106,7 +106,7 @@ public class PatternsRequestConditionTests {
|
|||
assertNotNull(match);
|
||||
assertEquals("/{foo}.*", match.getPatterns().iterator().next());
|
||||
|
||||
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false);
|
||||
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false);
|
||||
match = condition.getMatchingCondition(request);
|
||||
|
||||
assertNotNull(match);
|
||||
|
@ -121,15 +121,19 @@ public class PatternsRequestConditionTests {
|
|||
PatternsRequestCondition match = condition.getMatchingCondition(request);
|
||||
|
||||
assertNotNull(match);
|
||||
assertEquals("/foo/", match.getPatterns().iterator().next());
|
||||
assertEquals("Should match by default", "/foo/", match.getPatterns().iterator().next());
|
||||
|
||||
boolean useSuffixPatternMatch = false;
|
||||
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, useSuffixPatternMatch);
|
||||
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true);
|
||||
match = condition.getMatchingCondition(request);
|
||||
|
||||
assertNotNull(match);
|
||||
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
|
||||
"/foo/", match.getPatterns().iterator().next());
|
||||
|
||||
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false);
|
||||
match = condition.getMatchingCondition(request);
|
||||
|
||||
assertNull(match);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -317,7 +317,7 @@ public class RequestMappingInfoHandlerMappingTests {
|
|||
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
|
||||
if (annotation != null) {
|
||||
return new RequestMappingInfo(
|
||||
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true),
|
||||
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true, true),
|
||||
new RequestMethodsRequestCondition(annotation.method()),
|
||||
new ParamsRequestCondition(annotation.params()),
|
||||
new HeadersRequestCondition(annotation.headers()),
|
||||
|
|
Loading…
Reference in New Issue