diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index d43a947aff7..843499f94e3 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -21,6 +21,7 @@ Changes in version 3.1 RC2 (2011-11-15) * The Form input tag allows type values other than "text" such as HTML5-specific types. * The Form hidden tag supports "disabled" attribute * Add ignoreDefaultModelOnRedirect attribute to +* Add methods to UriComponentsBuilder for replacing the path or the query. Changes in version 3.1 RC1 (2011-10-11) --------------------------------------- diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 31555016f10..3b168d3f759 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -44,6 +44,7 @@ import org.springframework.util.StringUtils; * * * @author Arjen Poutsma + * @author Rossen Stoyanchev * @see #newInstance() * @see #fromPath(String) * @see #fromUri(URI) @@ -78,20 +79,20 @@ public class UriComponentsBuilder { "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?"); + + private String scheme; - private String scheme; + private String userInfo; - private String userInfo; + private String host; - private String host; - - private int port = -1; + private int port = -1; private PathComponentBuilder pathBuilder = NULL_PATH_COMPONENT_BUILDER; private final MultiValueMap queryParams = new LinkedMultiValueMap(); - private String fragment; + private String fragment; /** * Default constructor. Protected to prevent direct instantiation. @@ -101,42 +102,42 @@ public class UriComponentsBuilder { * @see #fromUri(URI) */ protected UriComponentsBuilder() { - } + } // Factory methods - /** - * Returns a new, empty builder. - * - * @return the new {@code UriComponentsBuilder} - */ - public static UriComponentsBuilder newInstance() { - return new UriComponentsBuilder(); - } + /** + * Returns a new, empty builder. + * + * @return the new {@code UriComponentsBuilder} + */ + public static UriComponentsBuilder newInstance() { + return new UriComponentsBuilder(); + } - /** - * Returns a builder that is initialized with the given path. - * - * @param path the path to initialize with - * @return the new {@code UriComponentsBuilder} - */ - public static UriComponentsBuilder fromPath(String path) { - UriComponentsBuilder builder = new UriComponentsBuilder(); - builder.path(path); - return builder; - } + /** + * Returns a builder that is initialized with the given path. + * + * @param path the path to initialize with + * @return the new {@code UriComponentsBuilder} + */ + public static UriComponentsBuilder fromPath(String path) { + UriComponentsBuilder builder = new UriComponentsBuilder(); + builder.path(path); + return builder; + } - /** - * Returns a builder that is initialized with the given {@code URI}. - * - * @param uri the URI to initialize with - * @return the new {@code UriComponentsBuilder} - */ - public static UriComponentsBuilder fromUri(URI uri) { - UriComponentsBuilder builder = new UriComponentsBuilder(); - builder.uri(uri); - return builder; - } + /** + * Returns a builder that is initialized with the given {@code URI}. + * + * @param uri the URI to initialize with + * @return the new {@code UriComponentsBuilder} + */ + public static UriComponentsBuilder fromUri(URI uri) { + UriComponentsBuilder builder = new UriComponentsBuilder(); + builder.uri(uri); + return builder; + } /** * Returns a builder that is initialized with the given URI string. @@ -201,132 +202,145 @@ public class UriComponentsBuilder { // build methods - /** - * Builds a {@code UriComponents} instance from the various components contained in this builder. - * - * @return the URI components - */ - public UriComponents build() { + /** + * Builds a {@code UriComponents} instance from the various components contained in this builder. + * + * @return the URI components + */ + public UriComponents build() { return build(false); } - /** - * Builds a {@code UriComponents} instance from the various components contained in this builder. - * + /** + * Builds a {@code UriComponents} instance from the various components contained in this builder. + * * @param encoded whether all the components set in this builder are encoded ({@code true}) or not ({@code false}). * @return the URI components - */ - public UriComponents build(boolean encoded) { + */ + public UriComponents build(boolean encoded) { return new UriComponents(scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true); - } + } // URI components methods - /** - * Initializes all components of this URI builder with the components of the given URI. - * - * @param uri the URI - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder uri(URI uri) { - Assert.notNull(uri, "'uri' must not be null"); - Assert.isTrue(!uri.isOpaque(), "Opaque URI [" + uri + "] not supported"); + /** + * Initializes all components of this URI builder with the components of the given URI. + * + * @param uri the URI + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder uri(URI uri) { + Assert.notNull(uri, "'uri' must not be null"); + Assert.isTrue(!uri.isOpaque(), "Opaque URI [" + uri + "] not supported"); - this.scheme = uri.getScheme(); + this.scheme = uri.getScheme(); - if (uri.getUserInfo() != null) { - this.userInfo = uri.getUserInfo(); - } - if (uri.getHost() != null) { - this.host = uri.getHost(); - } - if (uri.getPort() != -1) { - this.port = uri.getPort(); - } - if (StringUtils.hasLength(uri.getPath())) { + if (uri.getUserInfo() != null) { + this.userInfo = uri.getUserInfo(); + } + if (uri.getHost() != null) { + this.host = uri.getHost(); + } + if (uri.getPort() != -1) { + this.port = uri.getPort(); + } + if (StringUtils.hasLength(uri.getPath())) { this.pathBuilder = new FullPathComponentBuilder(uri.getPath()); - } - if (StringUtils.hasLength(uri.getQuery())) { + } + if (StringUtils.hasLength(uri.getQuery())) { this.queryParams.clear(); query(uri.getQuery()); - } - if (uri.getFragment() != null) { - this.fragment = uri.getFragment(); - } - return this; - } - - /** - * Sets the URI scheme. The given scheme may contain URI template variables, and may also be {@code null} to clear the - * scheme of this builder. - * - * @param scheme the URI scheme - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder scheme(String scheme) { - this.scheme = scheme; - return this; - } - - /** - * Sets the URI user info. The given user info may contain URI template variables, and may also be {@code null} to - * clear the user info of this builder. - * - * @param userInfo the URI user info - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder userInfo(String userInfo) { - this.userInfo = userInfo; - return this; - } - - /** - * Sets the URI host. The given host may contain URI template variables, and may also be {@code null} to clear the host - * of this builder. - * - * @param host the URI host - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder host(String host) { - this.host = host; - return this; - } - - /** - * Sets the URI port. Passing {@code -1} will clear the port of this builder. - * - * @param port the URI port - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder port(int port) { - Assert.isTrue(port >= -1, "'port' must not be < -1"); - this.port = port; - return this; - } + } + if (uri.getFragment() != null) { + this.fragment = uri.getFragment(); + } + return this; + } /** - * Appends the given path to the existing path of this builder. The given path may contain URI template variables. - * - * @param path the URI path - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder path(String path) { + * Sets the URI scheme. The given scheme may contain URI template variables, and may also be {@code null} to clear the + * scheme of this builder. + * + * @param scheme the URI scheme + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder scheme(String scheme) { + this.scheme = scheme; + return this; + } + + /** + * Sets the URI user info. The given user info may contain URI template variables, and may also be {@code null} to + * clear the user info of this builder. + * + * @param userInfo the URI user info + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder userInfo(String userInfo) { + this.userInfo = userInfo; + return this; + } + + /** + * Sets the URI host. The given host may contain URI template variables, and may also be {@code null} to clear the host + * of this builder. + * + * @param host the URI host + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder host(String host) { + this.host = host; + return this; + } + + /** + * Sets the URI port. Passing {@code -1} will clear the port of this builder. + * + * @param port the URI port + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder port(int port) { + Assert.isTrue(port >= -1, "'port' must not be < -1"); + this.port = port; + return this; + } + + /** + * Appends the given path to the existing path of this builder. The given path may contain URI template variables. + * + * @param path the URI path + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder path(String path) { if (path != null) { this.pathBuilder = this.pathBuilder.appendPath(path); - } else { + } + else { this.pathBuilder = NULL_PATH_COMPONENT_BUILDER; } return this; - } + } - /** - * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI - * template variables. - * - * @param pathSegments the URI path segments - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException { + /** + * Sets the path of this builder overriding all existing path and path segment values. + * + * @param path the URI path; a {@code null} value results in an empty path. + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder replacePath(String path) { + this.pathBuilder = NULL_PATH_COMPONENT_BUILDER; + path(path); + return this; + } + + /** + * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI + * template variables. + * + * @param pathSegments the URI path segments + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException { Assert.notNull(pathSegments, "'segments' must not be null"); this.pathBuilder = this.pathBuilder.appendPathSegments(pathSegments); return this; @@ -335,7 +349,7 @@ public class UriComponentsBuilder { /** * Appends the given query to the existing query of this builder. The given query may contain URI template variables. * - * @param query the URI path + * @param query the query string * @return this UriComponentsBuilder */ public UriComponentsBuilder query(String query) { @@ -348,52 +362,78 @@ public class UriComponentsBuilder { } } else { - queryParams.clear(); + this.queryParams.clear(); } return this; } + /** + * Sets the query of this builder overriding all existing query parameters. + * + * @param query the query string; a {@code null} value removes all query parameters. + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder replaceQuery(String query) { + this.queryParams.clear(); + query(query); + return this; + } - /** - * Appends the given query parameter to the existing query parameters. The given name or any of the values may contain - * URI template variables. If no values are given, the resulting URI will contain the query parameter name only (i.e. - * {@code ?foo} instead of {@code ?foo=bar}. - * - * @param name the query parameter name - * @param values the query parameter values - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder queryParam(String name, Object... values) { + /** + * Appends the given query parameter to the existing query parameters. The given name or any of the values may contain + * URI template variables. If no values are given, the resulting URI will contain the query parameter name only (i.e. + * {@code ?foo} instead of {@code ?foo=bar}. + * + * @param name the query parameter name + * @param values the query parameter values + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder queryParam(String name, Object... values) { Assert.notNull(name, "'name' must not be null"); if (!ObjectUtils.isEmpty(values)) { for (Object value : values) { String valueAsString = value != null ? value.toString() : null; - queryParams.add(name, valueAsString); + this.queryParams.add(name, valueAsString); } } else { - queryParams.add(name, null); + this.queryParams.add(name, null); } return this; } - /** - * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear - * the fragment of this builder. - * - * @param fragment the URI fragment - * @return this UriComponentsBuilder - */ - public UriComponentsBuilder fragment(String fragment) { - if (fragment != null) { - Assert.hasLength(fragment, "'fragment' must not be empty"); - this.fragment = fragment; - } - else { - this.fragment = null; - } - return this; - } + /** + * Sets the query parameter values overriding all existing query values for the same parameter. + * If no values are given, the resulting URI will contain the query parameter name only. + * + * @param name the query parameter name + * @param values the query parameter values + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder replaceQueryParam(String name, Object... values) { + Assert.notNull(name, "'name' must not be null"); + this.queryParams.remove(name); + queryParam(name, values); + return this; + } + + /** + * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear + * the fragment of this builder. + * + * @param fragment the URI fragment + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder fragment(String fragment) { + if (fragment != null) { + Assert.hasLength(fragment, "'fragment' must not be empty"); + this.fragment = fragment; + } + else { + this.fragment = null; + } + return this; + } /** * Represents a builder for {@link org.springframework.web.util.UriComponents.PathComponent} @@ -403,7 +443,7 @@ public class UriComponentsBuilder { UriComponents.PathComponent build(); PathComponentBuilder appendPath(String path); - + PathComponentBuilder appendPathSegments(String... pathSegments); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index 8e1af12c5f3..2dfc28bec1e 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -174,6 +174,36 @@ public class UriComponentsBuilderTests { assertEquals(Arrays.asList("foo"), result.getPathSegments()); } + @Test + public void replacePath() { + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt"); + builder.replacePath("/rfc/rfc3986.txt"); + UriComponents result = builder.build(); + + assertEquals("http://www.ietf.org/rfc/rfc3986.txt", result.toUriString()); + + builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt"); + builder.replacePath(null); + result = builder.build(); + + assertEquals("http://www.ietf.org", result.toUriString()); + } + + @Test + public void replaceQuery() { + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux"); + builder.replaceQuery("baz=42"); + UriComponents result = builder.build(); + + assertEquals("http://example.com/foo?baz=42", result.toUriString()); + + builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux"); + builder.replaceQuery(null); + result = builder.build(); + + assertEquals("http://example.com/foo", result.toUriString()); + } + @Test public void queryParams() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); @@ -197,4 +227,20 @@ public class UriComponentsBuilderTests { assertEquals(expectedQueryParams, result.getQueryParams()); } + + @Test + public void replaceQueryParam() { + UriComponentsBuilder builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42); + builder.replaceQueryParam("baz", "xuq", 24); + UriComponents result = builder.build(); + + assertEquals("baz=xuq&baz=24", result.getQuery()); + + builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42); + builder.replaceQueryParam("baz"); + result = builder.build(); + + assertEquals("baz", result.getQuery()); + } + } diff --git a/spring-framework-reference/src/mvc.xml b/spring-framework-reference/src/mvc.xml index e5fbd416cea..e821d25ebca 100644 --- a/spring-framework-reference/src/mvc.xml +++ b/spring-framework-reference/src/mvc.xml @@ -1792,7 +1792,9 @@ public class EditPetForm { so no attributes should be passed on to RedirectView. Both the MVC namespace and the MVC Java config (via @EnableWebMvc) - automatically set this flag to true. + keep this flag set to false in order to maintain + backwards compatibility. However, for new applications we recommend + setting it to true The RedirectAttributes interface can also be used to add flash attributes. Unlike other redirect diff --git a/spring-framework-reference/src/new-in-3.1.xml b/spring-framework-reference/src/new-in-3.1.xml index b73e5c7a5ea..ed6ea79bf22 100644 --- a/spring-framework-reference/src/new-in-3.1.xml +++ b/spring-framework-reference/src/new-in-3.1.xml @@ -467,5 +467,25 @@ linkend="mvc-multipart-forms-non-browsers" /> and . + +
+ <classname>UriComponentsBuilder</classname> and <classname>UriComponents</classname> + + A new UriComponents class has been added, + which is an immutable container of URI components providing + access to all contained URI components. + A nenw UriComponentsBuilder class is also + provided to help create UriComponents instances. + Together the two classes give fine-grained control over all + aspects of preparing a URI including construction, expansion + from URI template variables, and encoding. + + In most cases the new classes can be used as a more flexible + alternative to the existing UriTemplate + especially since UriTemplate relies on those + same classes internally. + +
+ diff --git a/spring-framework-reference/src/remoting.xml b/spring-framework-reference/src/remoting.xml index 70f5306f041..d26c1b61540 100644 --- a/spring-framework-reference/src/remoting.xml +++ b/spring-framework-reference/src/remoting.xml @@ -1434,6 +1434,56 @@ URI location = template.postForLocation(uri, booking, "1"); information on using the execute method and the meaning of its other method arguments. +
+ Working with the URI + + For each of the main HTTP methods, the RestTemplate + provides variants that either take a String URI or java.net.URI + as the first argument. + + + The String URI variants accept template arguments as a + String variable length argument or as a Map<String,String>. + They also assume the URL String is not encoded and needs to be encoded. + For example the following: + + + restTemplate.getForObject("http://example.com/hotel list", String.class); + + will perform a GET on http://example.com/hotel%20list. + That means if the input URL String is already encoded, it will be encoded twice -- + i.e. http://example.com/hotel%20list will become + http://example.com/hotel%2520list. + If this is not the intended effect, use the + java.net.URI method variant, which assumes + the URL is already encoded is also generally useful if you want + to reuse a single (fully expanded) URI + multiple times. + + The UriComponentsBuilder class can be used + to build and encode the URI including support + for URI templates. For example you can start with a URL String: + + + UriComponents uriComponents = + UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}/bookings/{booking}").build() + .expand("42", "21") + .encode(); + +URI uri = uriComponents.toUri(); + + Or specify each URI component indiviudally: + + UriComponents uriComponents = + UriComponentsBuilder.newInstance() + .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build() + .expand("42", "21") + .encode(); + +URI uri = uriComponents.toUri(); + +
+
Dealing with request and response headers