From 9f4480a1b8b7d39cbfb4c9c90c340edf4bf161fb Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 26 May 2009 12:25:22 +0000 Subject: [PATCH] SPR-5774 - UriTemplate not matching querystrings --- .../springframework/web/util/UriTemplate.java | 122 ++++++++++-------- .../web/util/UriTemplateTests.java | 43 +++--- 2 files changed, 96 insertions(+), 69 deletions(-) diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java index ea3a0c7be72..2bc3521e088 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriTemplate.java @@ -29,37 +29,31 @@ import java.util.regex.Pattern; import org.springframework.util.Assert; /** - * Represents a URI template. An URI template is a URI-like String that contained variables - * marked of in braces ({, }), which can be expanded to produce a URI. - *

See {@link #expand(Map)}, {@link #expand(String[])}, and {@link #match(String)} for example usages. + * Represents a URI template. An URI template is a URI-like String that contained variables marked of in braces + * ({, }), which can be expanded to produce a URI.

See {@link #expand(Map)}, {@link + * #expand(String[])}, and {@link #match(String)} for example usages. * * @author Arjen Poutsma - * @since 3.0 * @see URI Templates * @since 3.0 */ public class UriTemplate { - /** - * Captures URI template variable names. - */ + /** Captures URI template variable names. */ private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); - /** - * Replaces template variables in the URI template. - */ + /** Replaces template variables in the URI template. */ private static final String VALUE_REGEX = "(.*)"; - private final List variableNames; private final Pattern matchPattern; private final String uriTemplate; - /** * Construct a new {@link UriTemplate} with the given URI String. + * * @param uriTemplate the URI template string */ public UriTemplate(String uriTemplate) { @@ -69,9 +63,9 @@ public class UriTemplate { this.matchPattern = parser.getMatchPattern(); } - /** * Return the names of the variables in the template, in order. + * * @return the template variable names */ public final List getVariableNames() { @@ -79,10 +73,8 @@ public class UriTemplate { } /** - * Given the Map of variables, expands this template into a URI. - * The Map keys represent variable names, the Map values variable values. - * The order of variables is not significant. - *

Example: + * Given the Map of variables, expands this template into a URI. The Map keys represent variable names, the Map values + * variable values. The order of variables is not significant.

Example: *

 	 * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
 	 * Map<String, String> uriVariables = new HashMap<String, String>();
@@ -91,10 +83,11 @@ public class UriTemplate {
 	 * System.out.println(template.expand(uriVariables));
 	 * 
* will print:
http://example.com/hotels/1/bookings/42
+ * * @param uriVariables the map of URI variables * @return the expanded URI - * @throws IllegalArgumentException if uriVariables is null; - * or if it does not contain values for all the variable names + * @throws IllegalArgumentException if uriVariables is null; or if it does not contain values + * for all the variable names */ public URI expand(Map uriVariables) { Assert.notNull(uriVariables, "'uriVariables' must not be null"); @@ -110,25 +103,22 @@ public class UriTemplate { } /** - * Given an array of variables, expand this template into a full URI. - * The array represent variable values. The order of variables is significant. - *

Example: - *

-	 * will print: 
http://example.com/hotels/1/bookings/42
+ * Given an array of variables, expand this template into a full URI. The array represent variable values. The order of + * variables is significant.

Example:

 will print: 
http://example.com/hotels/1/bookings/42
+ * * @param uriVariableValues the array of URI variables * @return the expanded URI - * @throws IllegalArgumentException if uriVariables is null; - * or if it does not contain sufficient variables + * @throws IllegalArgumentException if uriVariables is null; or if it does not contain + * sufficient variables */ public URI expand(String... uriVariableValues) { Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); if (uriVariableValues.length != this.variableNames.size()) { - throw new IllegalArgumentException("Invalid amount of variables values in [" + - this.uriTemplate + "]: expected " + this.variableNames.size() + - "; got " + uriVariableValues.length); + throw new IllegalArgumentException( + "Invalid amount of variables values in [" + this.uriTemplate + "]: expected " + + this.variableNames.size() + "; got " + uriVariableValues.length); } Matcher matcher = NAMES_PATTERN.matcher(this.uriTemplate); StringBuffer buffer = new StringBuffer(); @@ -143,6 +133,7 @@ public class UriTemplate { /** * Indicate whether the given URI matches this template. + * * @param uri the URI to match to * @return true if it matches; false otherwise */ @@ -155,14 +146,11 @@ public class UriTemplate { } /** - * Match the given URI to a map of variable values. Keys in the returned map are - * variable names, values are variable values, as occurred in the given URI. - *

Example: - *

-	 * UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
-	 * System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
-	 * 
- * will print:
{hotel=1, booking=42}
+ * Match the given URI to a map of variable values. Keys in the returned map are variable names, values are variable + * values, as occurred in the given URI.

Example:

 UriTemplate template = new
+	 * UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
+	 * 
will print:
{hotel=1, booking=42}
+ * * @param uri the URI to match to * @return a map of variable values */ @@ -185,30 +173,57 @@ public class UriTemplate { return this.uriTemplate; } - private static URI encodeUri(String uri) { try { - int idx = uri.indexOf(':'); - URI result; - if (idx != -1) { - String scheme = uri.substring(0, idx); - String ssp = uri.substring(idx + 1); - result = new URI(scheme, ssp, null); + int schemeIdx = uri.indexOf(':'); + if (schemeIdx == -1) { + int queryIdx = uri.indexOf('?'); + if (queryIdx == -1) { + int fragmentIdx = uri.indexOf('#'); + if (fragmentIdx == -1) { + return new URI(null, null, uri, null); + } + else { + String path = uri.substring(0, fragmentIdx); + String fragment = uri.substring(fragmentIdx + 1); + return new URI(null, null, path, fragment); + } + } + else { + int fragmentIdx = uri.indexOf('#', queryIdx + 1); + if (fragmentIdx == -1) { + String path = uri.substring(0, queryIdx); + String query = uri.substring(queryIdx + 1); + return new URI(null, null, path, query, null); + } + else { + String path = uri.substring(0, queryIdx); + String query = uri.substring(queryIdx + 1, fragmentIdx); + String fragment = uri.substring(fragmentIdx + 1); + return new URI(null, null, path, query, fragment); + } + } } else { - result = new URI(null, null, uri, null); + int fragmentIdx = uri.indexOf('#', schemeIdx + 1); + String scheme = uri.substring(0, schemeIdx); + if (fragmentIdx == -1) { + String ssp = uri.substring(schemeIdx + 1); + return new URI(scheme, ssp, null); + } + else { + String ssp = uri.substring(schemeIdx + 1, fragmentIdx); + String fragment = uri.substring(fragmentIdx + 1); + return new URI(scheme, ssp, fragment); + } } - return result; } catch (URISyntaxException ex) { throw new IllegalArgumentException("Could not create URI from [" + uri + "]: " + ex); } } - - /** - * Static inner class to parse uri template strings into a matching regular expression. - */ + /** Static inner class to parse uri template strings into a matching regular expression. */ private static class Parser { private final List variableNames = new LinkedList(); @@ -240,7 +255,6 @@ public class UriTemplate { return Pattern.quote(result); } - private List getVariableNames() { return Collections.unmodifiableList(this.variableNames); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java index 2a3d46b5290..10c2c4a20aa 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriTemplateTests.java @@ -24,41 +24,34 @@ import java.util.List; import java.util.Map; import static org.junit.Assert.*; -import org.junit.Before; import org.junit.Test; -/** - * @author Arjen Poutsma - */ +/** @author Arjen Poutsma */ public class UriTemplateTests { - private UriTemplate template; - - @Before - public void create() { - template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); - } - @Test public void getVariableNames() throws Exception { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); List variableNames = template.getVariableNames(); assertEquals("Invalid variable names", Arrays.asList("hotel", "booking"), variableNames); } @Test public void expandVarArgs() throws Exception { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); URI result = template.expand("1", "42"); assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result); } @Test(expected = IllegalArgumentException.class) public void expandVarArgsInvalidAmountVariables() throws Exception { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); template.expand("1", "42", "100"); } @Test public void expandMapDuplicateVariables() throws Exception { - template = new UriTemplate("/order/{c}/{c}/{c}"); + UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}"); assertEquals("Invalid variable names", Arrays.asList("c", "c", "c"), template.getVariableNames()); URI result = template.expand(Collections.singletonMap("c", "cheeseburger")); assertEquals("Invalid expanded template", new URI("/order/cheeseburger/cheeseburger/cheeseburger"), result); @@ -69,12 +62,14 @@ public class UriTemplateTests { Map uriVariables = new HashMap(2); uriVariables.put("booking", "42"); uriVariables.put("hotel", "1"); + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); URI result = template.expand(uriVariables); assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result); } @Test(expected = IllegalArgumentException.class) public void expandMapInvalidAmountVariables() throws Exception { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); template.expand(Collections.singletonMap("hotel", "1")); } @@ -83,12 +78,13 @@ public class UriTemplateTests { Map uriVariables = new HashMap(2); uriVariables.put("booking", "42"); uriVariables.put("bar", "1"); + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); template.expand(uriVariables); } @Test public void expandEncoded() throws Exception { - template = new UriTemplate("http://example.com/hotel list/{hotel}"); + UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}"); URI result = template.expand(Collections.singletonMap("hotel", "foo bar \u20AC")); assertEquals("Invalid expanded template", new URI("http", "//example.com/hotel list/foo bar \u20AC", null), result); @@ -98,6 +94,7 @@ public class UriTemplateTests { @Test public void matches() throws Exception { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); assertTrue("UriTemplate does not match", template.matches("http://example.com/hotels/1/bookings/42")); assertFalse("UriTemplate matches", template.matches("http://example.com/hotels/bookings")); assertFalse("UriTemplate matches", template.matches("")); @@ -110,13 +107,14 @@ public class UriTemplateTests { expected.put("booking", "42"); expected.put("hotel", "1"); + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); Map result = template.match("http://example.com/hotels/1/bookings/42"); assertEquals("Invalid match", expected, result); } @Test public void matchDuplicate() throws Exception { - template = new UriTemplate("/order/{c}/{c}/{c}"); + UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}"); Map result = template.match("/order/cheeseburger/cheeseburger/cheeseburger"); Map expected = Collections.singletonMap("c", "cheeseburger"); assertEquals("Invalid match", expected, result); @@ -124,11 +122,26 @@ public class UriTemplateTests { @Test public void matchMultipleInOneSegment() throws Exception { - template = new UriTemplate("/{foo}-{bar}"); + UriTemplate template = new UriTemplate("/{foo}-{bar}"); Map result = template.match("/12-34"); Map expected = new HashMap(2); expected.put("foo", "12"); expected.put("bar", "34"); assertEquals("Invalid match", expected, result); } + + @Test + public void queryVariables() throws Exception { + UriTemplate template = new UriTemplate("/search?q={query}"); + assertTrue(template.matches("/search?q=foo")); + } + + @Test + public void fragments() throws Exception { + UriTemplate template = new UriTemplate("/search#{fragment}"); + assertTrue(template.matches("/search#foo")); + + template = new UriTemplate("/search?query={query}#{fragment}"); + assertTrue(template.matches("/search?query=foo#bar")); + } } \ No newline at end of file