SPR-8898 Allow match by trailing slash in RequestMappingHandlerMapping.

This commit is contained in:
Rossen Stoyanchev 2011-12-08 03:38:50 +00:00
parent 569426dfdf
commit 6f150e4f07
5 changed files with 59 additions and 27 deletions

View File

@ -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)

View File

@ -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();

View File

@ -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()),

View File

@ -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

View File

@ -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()),