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 .
+
+
+ UriComponentsBuilder and UriComponents
+
+ 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