SPR-5251: URI Templates in @RequestMapping
This commit is contained in:
@@ -16,6 +16,11 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* PathMatcher implementation for Ant-style path patterns.
|
||||
* Examples are provided below.
|
||||
@@ -56,6 +61,10 @@ public class AntPathMatcher implements PathMatcher {
|
||||
/** Default path separator: "/" */
|
||||
public static final String DEFAULT_PATH_SEPARATOR = "/";
|
||||
|
||||
/** Captures URI template variable names. */
|
||||
private static final Pattern URI_TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{([\\w-~_\\.]+?)\\}");
|
||||
|
||||
|
||||
private String pathSeparator = DEFAULT_PATH_SEPARATOR;
|
||||
|
||||
|
||||
@@ -91,6 +100,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
* <code>false</code> if it didn't
|
||||
*/
|
||||
protected boolean doMatch(String pattern, String path, boolean fullMatch) {
|
||||
pattern = uriTemplateToAntPattern(pattern);
|
||||
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
|
||||
return false;
|
||||
}
|
||||
@@ -119,14 +129,13 @@ public class AntPathMatcher implements PathMatcher {
|
||||
if (pathIdxStart > pathIdxEnd) {
|
||||
// Path is exhausted, only match if rest of pattern is * or **'s
|
||||
if (pattIdxStart > pattIdxEnd) {
|
||||
return (pattern.endsWith(this.pathSeparator) ?
|
||||
path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
|
||||
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
|
||||
!path.endsWith(this.pathSeparator));
|
||||
}
|
||||
if (!fullMatch) {
|
||||
return true;
|
||||
}
|
||||
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") &&
|
||||
path.endsWith(this.pathSeparator)) {
|
||||
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
|
||||
@@ -187,17 +196,17 @@ public class AntPathMatcher implements PathMatcher {
|
||||
int foundIdx = -1;
|
||||
|
||||
strLoop:
|
||||
for (int i = 0; i <= strLength - patLength; i++) {
|
||||
for (int j = 0; j < patLength; j++) {
|
||||
String subPat = (String) pattDirs[pattIdxStart + j + 1];
|
||||
String subStr = (String) pathDirs[pathIdxStart + i + j];
|
||||
if (!matchStrings(subPat, subStr)) {
|
||||
continue strLoop;
|
||||
}
|
||||
}
|
||||
foundIdx = pathIdxStart + i;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i <= strLength - patLength; i++) {
|
||||
for (int j = 0; j < patLength; j++) {
|
||||
String subPat = (String) pattDirs[pattIdxStart + j + 1];
|
||||
String subStr = (String) pathDirs[pathIdxStart + i + j];
|
||||
if (!matchStrings(subPat, subStr)) {
|
||||
continue strLoop;
|
||||
}
|
||||
}
|
||||
foundIdx = pathIdxStart + i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundIdx == -1) {
|
||||
return false;
|
||||
@@ -382,7 +391,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
|
||||
String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Add any path parts that have a wildcarded pattern part.
|
||||
int puts = 0;
|
||||
@@ -390,9 +399,9 @@ public class AntPathMatcher implements PathMatcher {
|
||||
String patternPart = patternParts[i];
|
||||
if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
|
||||
if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
|
||||
buffer.append(this.pathSeparator);
|
||||
builder.append(this.pathSeparator);
|
||||
}
|
||||
buffer.append(pathParts[i]);
|
||||
builder.append(pathParts[i]);
|
||||
puts++;
|
||||
}
|
||||
}
|
||||
@@ -400,12 +409,51 @@ public class AntPathMatcher implements PathMatcher {
|
||||
// Append any trailing path parts.
|
||||
for (int i = patternParts.length; i < pathParts.length; i++) {
|
||||
if (puts > 0 || i > 0) {
|
||||
buffer.append(this.pathSeparator);
|
||||
builder.append(this.pathSeparator);
|
||||
}
|
||||
buffer.append(pathParts[i]);
|
||||
builder.append(pathParts[i]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces URI template variables with Ant-style pattern patchs. Looks for variables within curly braces, and replaces
|
||||
* those with <code>*</code>.
|
||||
*
|
||||
* <p/>For example: <code>/hotels/{hotel}/bookings</code> becomes
|
||||
* <code>/hotels/*/bookings</code>
|
||||
*
|
||||
* @param pattern the pattern, possibly containing URI template variables
|
||||
* @return the Ant-stlye pattern path
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
*/
|
||||
private static String uriTemplateToAntPattern(String pattern) {
|
||||
Matcher matcher = URI_TEMPLATE_NAMES_PATTERN.matcher(pattern);
|
||||
return matcher.replaceAll("*");
|
||||
}
|
||||
|
||||
|
||||
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
|
||||
if (pattern.contains("**") && pattern.contains("{")) {
|
||||
throw new IllegalArgumentException("Combining '**' and URI templates is not allowed");
|
||||
}
|
||||
String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
|
||||
String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
|
||||
|
||||
Map<String, String> variables = new LinkedHashMap<String, String>();
|
||||
|
||||
for (int i = 0; i < patternParts.length && i < pathParts.length; i++) {
|
||||
String patternPart = patternParts[i];
|
||||
String pathPart = pathParts[i];
|
||||
int patternEnd = patternPart.length() -1 ;
|
||||
if (patternEnd > 1 && patternPart.charAt(0) == '{' && patternPart.charAt(patternEnd) == '}') {
|
||||
String varName = patternPart.substring(1, patternEnd);
|
||||
variables.put(varName, pathPart);
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Strategy interface for <code>String</code>-based path matching.
|
||||
*
|
||||
@@ -88,4 +90,16 @@ public interface PathMatcher {
|
||||
*/
|
||||
String extractPathWithinPattern(String pattern, String path);
|
||||
|
||||
/**
|
||||
* Given a pattern and a full path, extract the URI template variables. URI template
|
||||
* variables are expressed through curly brackets ('{' and '}').
|
||||
*
|
||||
* <p>For example: For pattern "/hotels/{hotel}" and path "/hotels/1", this method will
|
||||
* return a map containing "hotel"->"1".
|
||||
*
|
||||
* @param pattern the path pattern, possibly containing URI templates
|
||||
* @param path the full path to extract template variables from
|
||||
* @return a map, containing variable names as keys; variables values as values
|
||||
*/
|
||||
Map<String, String> extractUriTemplateVariables(String pattern, String path);
|
||||
}
|
||||
|
||||
@@ -16,18 +16,31 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Alef Arendsen
|
||||
* @author Seth Ladd
|
||||
* @author Juergen Hoeller
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class PathMatcherTests extends TestCase {
|
||||
public class AntPathMatcherTests {
|
||||
|
||||
public void testAntPathMatcher() {
|
||||
PathMatcher pathMatcher = new AntPathMatcher();
|
||||
private AntPathMatcher pathMatcher;
|
||||
|
||||
@Before
|
||||
public void createMatcher() {
|
||||
pathMatcher = new AntPathMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void standard() {
|
||||
// test exact matching
|
||||
assertTrue(pathMatcher.match("test", "test"));
|
||||
assertTrue(pathMatcher.match("/test", "/test"));
|
||||
@@ -109,9 +122,8 @@ public class PathMatcherTests extends TestCase {
|
||||
assertTrue(pathMatcher.match("", ""));
|
||||
}
|
||||
|
||||
public void testAntPathMatcherWithMatchStart() {
|
||||
PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
@Test
|
||||
public void withMatchStart() {
|
||||
// test exact matching
|
||||
assertTrue(pathMatcher.matchStart("test", "test"));
|
||||
assertTrue(pathMatcher.matchStart("/test", "/test"));
|
||||
@@ -197,8 +209,8 @@ public class PathMatcherTests extends TestCase {
|
||||
assertTrue(pathMatcher.matchStart("", ""));
|
||||
}
|
||||
|
||||
public void testAntPathMatcherWithUniqueDeliminator() {
|
||||
AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
@Test
|
||||
public void uniqueDeliminator() {
|
||||
pathMatcher.setPathSeparator(".");
|
||||
|
||||
// test exact matching
|
||||
@@ -259,9 +271,8 @@ public class PathMatcherTests extends TestCase {
|
||||
assertFalse(pathMatcher.match(".*bla.test", "XXXbl.test"));
|
||||
}
|
||||
|
||||
public void testAntPathMatcherExtractPathWithinPattern() throws Exception {
|
||||
PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
@Test
|
||||
public void extractPathWithinPattern() throws Exception {
|
||||
assertEquals("", pathMatcher.extractPathWithinPattern("/docs/commit.html", "/docs/commit.html"));
|
||||
|
||||
assertEquals("cvs/commit", pathMatcher.extractPathWithinPattern("/docs/*", "/docs/cvs/commit"));
|
||||
@@ -282,4 +293,17 @@ public class PathMatcherTests extends TestCase {
|
||||
assertEquals("docs/cvs/commit.html", pathMatcher.extractPathWithinPattern("/d?cs/**/*.html", "/docs/cvs/commit.html"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractUriTemplateVariables() throws Exception {
|
||||
Map<String,String> result = pathMatcher.extractUriTemplateVariables("/hotels/{hotel}", "/hotels/1");
|
||||
assertEquals(Collections.singletonMap("hotel", "1"), result);
|
||||
|
||||
result = pathMatcher.extractUriTemplateVariables("/hotels/{hotel}/bookings/{booking}", "/hotels/1/bookings/2");
|
||||
Map<String, String> expected = new LinkedHashMap<String, String>();
|
||||
expected.put("hotel", "1");
|
||||
expected.put("booking", "2");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user