diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
new file mode 100644
index 0000000000..8daaf768a0
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.util;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Default implementation of {@link UriBuilderFactory} using
+ * {@link UriComponentsBuilder} for building, encoding, and expanding URI
+ * templates.
+ *
+ *
Exposes configuration properties that customize the creation of all URIs
+ * built through this factory instance including a base URI, default URI
+ * variables, and an encoding mode.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.0
+ */
+public class DefaultUriBuilderFactory implements UriBuilderFactory {
+
+ public enum EncodingMode {URI_COMPONENT, VALUES_ONLY, NONE };
+
+
+ private final UriComponentsBuilder baseUri;
+
+ private final Map defaultUriVariables = new HashMap<>();
+
+ private EncodingMode encodingMode = EncodingMode.URI_COMPONENT;
+
+
+ /**
+ * Default constructor without a base URI.
+ */
+ public DefaultUriBuilderFactory() {
+ this(UriComponentsBuilder.fromPath(null));
+ }
+
+ /**
+ * Constructor with a String "base URI".
+ * The String given here is used to create a single "base"
+ * {@code UriComponentsBuilder}. Each time a new URI is prepared via
+ * {@link #uriString(String)} a new {@code UriComponentsBuilder} is created and
+ * merged with a clone of the "base" {@code UriComponentsBuilder}.
+ *
Note that the base URI may contain any or all components of a URI and
+ * those will apply to every URI.
+ */
+ public DefaultUriBuilderFactory(String baseUri) {
+ this(UriComponentsBuilder.fromUriString(baseUri));
+ }
+
+ /**
+ * Alternate constructor with a {@code UriComponentsBuilder} as the base URI.
+ */
+ public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) {
+ Assert.notNull(baseUri, "'baseUri' is required.");
+ this.baseUri = baseUri;
+ }
+
+
+ /**
+ * Configure default URI variable values to use when expanding a URI with a
+ * Map of values. The map supplied when expanding a given URI can override
+ * default values.
+ * @param defaultUriVariables the default URI variables
+ */
+ public void setDefaultUriVariables(Map defaultUriVariables) {
+ this.defaultUriVariables.clear();
+ if (defaultUriVariables != null) {
+ this.defaultUriVariables.putAll(defaultUriVariables);
+ }
+ }
+
+ /**
+ * Return the configured default URI variable values.
+ */
+ public Map getDefaultUriVariables() {
+ return Collections.unmodifiableMap(this.defaultUriVariables);
+ }
+
+ /**
+ * Specify the encoding mode to use when building URIs:
+ *
+ * - URI_COMPONENT -- expand the URI variables first and then encode all URI
+ * component (e.g. host, path, query, etc) according to the encoding rules
+ * for each individual component.
+ *
- VALUES_ONLY -- encode URI variable values only, prior to expanding
+ * them, using a "strict" encoding mode, i.e. encoding all characters
+ * outside the unreserved set as defined in
+ * RFC 3986 Section 2.
+ * This ensures a URI variable value will not contain any characters with a
+ * reserved purpose.
+ *
- NONE -- in this mode no encoding is performed.
+ *
+ * By default this is set to {@code "URI_COMPONENT"}.
+ * @param encodingMode the encoding mode to use
+ */
+ public void setEncodingMode(EncodingMode encodingMode) {
+ this.encodingMode = encodingMode;
+ }
+
+ /**
+ * Return the configured encoding mode.
+ */
+ public EncodingMode getEncodingMode() {
+ return this.encodingMode;
+ }
+
+
+ // UriTemplateHandler
+
+ public URI expand(String uriTemplate, Map uriVars) {
+ return uriString(uriTemplate).build(uriVars);
+ }
+
+ public URI expand(String uriTemplate, Object... uriVars) {
+ return uriString(uriTemplate).build(uriVars);
+ }
+
+ // UriBuilderFactory
+
+ public UriBuilder uriString(String uriTemplate) {
+ return new DefaultUriBuilder(uriTemplate);
+ }
+
+
+ /**
+ * {@link DefaultUriBuilderFactory} specific implementation of UriBuilder.
+ */
+ private class DefaultUriBuilder implements UriBuilder {
+
+ private final UriComponentsBuilder uriComponentsBuilder;
+
+
+ public DefaultUriBuilder(String uriTemplate) {
+ this.uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
+ }
+
+ private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
+
+ // Merge base URI with child URI template
+ UriComponentsBuilder result = baseUri.cloneBuilder();
+ UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build();
+ result.uriComponents(child);
+
+ // Split path into path segments
+ List pathList = result.build().getPathSegments();
+ String[] pathArray = pathList.toArray(new String[pathList.size()]);
+ result.replacePath(null);
+ result.pathSegment(pathArray);
+
+ return result;
+ }
+
+ @Override
+ public DefaultUriBuilder scheme(String scheme) {
+ this.uriComponentsBuilder.scheme(scheme);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder userInfo(String userInfo) {
+ this.uriComponentsBuilder.userInfo(userInfo);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder host(String host) {
+ this.uriComponentsBuilder.host(host);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder port(int port) {
+ this.uriComponentsBuilder.port(port);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder port(String port) {
+ this.uriComponentsBuilder.port(port);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder path(String path) {
+ this.uriComponentsBuilder.path(path);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder replacePath(String path) {
+ this.uriComponentsBuilder.replacePath(path);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder pathSegment(String... pathSegments) {
+ this.uriComponentsBuilder.pathSegment(pathSegments);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder query(String query) {
+ this.uriComponentsBuilder.query(query);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder replaceQuery(String query) {
+ this.uriComponentsBuilder.replaceQuery(query);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder queryParam(String name, Object... values) {
+ this.uriComponentsBuilder.queryParam(name, values);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder replaceQueryParam(String name, Object... values) {
+ this.uriComponentsBuilder.replaceQueryParam(name, values);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder queryParams(MultiValueMap params) {
+ this.uriComponentsBuilder.queryParams(params);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder replaceQueryParams(MultiValueMap params) {
+ this.uriComponentsBuilder.replaceQueryParams(params);
+ return this;
+ }
+
+ @Override
+ public DefaultUriBuilder fragment(String fragment) {
+ this.uriComponentsBuilder.fragment(fragment);
+ return this;
+ }
+
+ @Override
+ public URI build(Map uriVars) {
+ if (!defaultUriVariables.isEmpty()) {
+ Map map = new HashMap<>();
+ map.putAll(defaultUriVariables);
+ map.putAll(uriVars);
+ uriVars = map;
+ }
+ if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
+ uriVars = UriUtils.encodeUriVariables(uriVars);
+ }
+ UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
+ if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
+ uriComponents = uriComponents.encode();
+ }
+ return URI.create(uriComponents.toString());
+ }
+
+ @Override
+ public URI build(Object... uriVars) {
+ if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) {
+ return build(Collections.emptyMap());
+ }
+ if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
+ uriVars = UriUtils.encodeUriVariables(uriVars);
+ }
+ UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
+ if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
+ uriComponents = uriComponents.encode();
+ }
+ return URI.create(uriComponents.toString());
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
index 0912430d07..905f29eef3 100644
--- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
+++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
@@ -446,14 +446,27 @@ final class HierarchicalUriComponents extends UriComponents {
@Override
protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) {
- builder.scheme(getScheme());
- builder.userInfo(getUserInfo());
- builder.host(getHost());
- builder.port(getPort());
- builder.replacePath("");
- this.path.copyToUriComponentsBuilder(builder);
- builder.replaceQueryParams(getQueryParams());
- builder.fragment(getFragment());
+ if (getScheme() != null) {
+ builder.scheme(getScheme());
+ }
+ if (getUserInfo() != null) {
+ builder.userInfo(getUserInfo());
+ }
+ if (getHost() != null) {
+ builder.host(getHost());
+ }
+ if (getPort() != -1) {
+ builder.port(getPort());
+ }
+ if (getPath() != null) {
+ this.path.copyToUriComponentsBuilder(builder);
+ }
+ if (!getQueryParams().isEmpty()) {
+ builder.queryParams(getQueryParams());
+ }
+ if (getFragment() != null) {
+ builder.fragment(getFragment());
+ }
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
index bf3c7e3495..f0e1c29575 100644
--- a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
+++ b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
@@ -137,9 +137,15 @@ final class OpaqueUriComponents extends UriComponents {
@Override
protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) {
- builder.scheme(getScheme());
- builder.schemeSpecificPart(getSchemeSpecificPart());
- builder.fragment(getFragment());
+ if (getScheme() != null) {
+ builder.scheme(getScheme());
+ }
+ if (getSchemeSpecificPart() != null) {
+ builder.schemeSpecificPart(getSchemeSpecificPart());
+ }
+ if (getFragment() != null) {
+ builder.fragment(getFragment());
+ }
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java
new file mode 100644
index 0000000000..2388d32581
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUUriBuilder WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.util;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.springframework.util.MultiValueMap;
+
+/**
+ * Builder-style methods to prepare and expand a URI template with variables.
+ *
+ * Effectively a generalization of {@link UriComponentsBuilder} but with
+ * shortcuts to expand directly into {@link URI} rather than
+ * {@link UriComponents} and also leaving common concerns such as encoding
+ * preferences, a base URI, and others as implementation concerns.
+ *
+ *
Typically obtained via {@link UriBuilderFactory} which serves as a central
+ * component configured once and used to create many URLs.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.0
+ * @see UriBuilderFactory
+ * @see UriComponentsBuilder
+ */
+public interface UriBuilder {
+
+ /**
+ * Set the URI scheme which may contain URI template variables,
+ * and may also be {@code null} to clear the scheme of this builder.
+ * @param scheme the URI scheme
+ */
+ UriBuilder scheme(String scheme);
+
+ /**
+ * Set the URI user info which 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
+ */
+ UriBuilder userInfo(String userInfo);
+
+ /**
+ * Set the URI host which may contain URI template variables, and may also
+ * be {@code null} to clear the host of this builder.
+ * @param host the URI host
+ */
+ UriBuilder host(String host);
+
+ /**
+ * Set the URI port. Passing {@code -1} will clear the port of this builder.
+ * @param port the URI port
+ */
+ UriBuilder port(int port);
+
+ /**
+ * Set the URI port . Use this method only when the port needs to be
+ * parameterized with a URI variable. Otherwise use {@link #port(int)}.
+ * Passing {@code null} will clear the port of this builder.
+ * @param port the URI port
+ */
+ UriBuilder port(String port);
+
+ /**
+ * Append the given path to the existing path of this builder.
+ * The given path may contain URI template variables.
+ * @param path the URI path
+ */
+ UriBuilder path(String path);
+
+ /**
+ * Set the path of this builder overriding the existing path values.
+ * @param path the URI path or {@code null} for an empty path.
+ */
+ UriBuilder replacePath(String path);
+
+ /**
+ * Append path segments to the existing path. Each path segment may contain
+ * URI template variables and should not contain any slashes.
+ * Use {@code path("/")} subsequently to ensure a trailing slash.
+ * @param pathSegments the URI path segments
+ */
+ UriBuilder pathSegment(String... pathSegments) throws IllegalArgumentException;
+
+ /**
+ * Append the given query to the existing query of this builder.
+ * The given query may contain URI template variables.
+ *
Note: The presence of reserved characters can prevent
+ * correct parsing of the URI string. For example if a query parameter
+ * contains {@code '='} or {@code '&'} characters, the query string cannot
+ * be parsed unambiguously. Such values should be substituted for URI
+ * variables to enable correct parsing:
+ *
+ * builder.query("filter={value}").uriString("hot&cold");
+ *
+ * @param query the query string
+ */
+ UriBuilder query(String query);
+
+ /**
+ * Set the query of this builder overriding all existing query parameters.
+ * @param query the query string or {@code null} to remove all query params
+ */
+ UriBuilder replaceQuery(String query);
+
+ /**
+ * Append 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
+ */
+ UriBuilder queryParam(String name, Object... values);
+
+ /**
+ * Add the given query parameters.
+ * @param params the params
+ */
+ UriBuilder queryParams(MultiValueMap params);
+
+ /**
+ * Set the query parameter values overriding all existing query values for
+ * the same parameter. If no values are given, the query parameter is removed.
+ * @param name the query parameter name
+ * @param values the query parameter values
+ */
+ UriBuilder replaceQueryParam(String name, Object... values);
+
+ /**
+ * Set the query parameter values overriding all existing query values.
+ * @param params the query parameter name
+ */
+ UriBuilder replaceQueryParams(MultiValueMap params);
+
+ /**
+ * Set 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
+ */
+ UriBuilder fragment(String fragment);
+
+ /**
+ * Build a {@link URI} instance and replaces URI template variables
+ * with the values from an array.
+ * @param uriVariables the map of URI variables
+ * @return the URI
+ */
+ URI build(Object... uriVariables);
+
+ /**
+ * Build a {@link URI} instance and replaces URI template variables
+ * with the values from a map.
+ * @param uriVariables the map of URI variables
+ * @return the URI
+ */
+ URI build(Map uriVariables);
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java
new file mode 100644
index 0000000000..504a7d5092
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.util;
+
+/**
+ * Factory for instances of {@link UriBuilder}.
+ *
+ * A single {@link UriBuilderFactory} may be created once, configured with
+ * common properties such as a base URI, and then used to create many URIs.
+ *
+ *
Extends {@link UriTemplateHandler} which has a similar purpose but only
+ * provides shortcuts for expanding URI templates, not builder style methods.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.0
+ */
+public interface UriBuilderFactory extends UriTemplateHandler {
+
+ /**
+ * Return a builder that is initialized with the given URI string which may
+ * be a URI template and represent full URI or just a path.
+ *
Depending on the factory implementation and configuration, the builder
+ * may merge the given URI string with a base URI and apply other operations.
+ * Refer to the specific factory implementation for details.
+ * @param uriTemplate the URI template to create the builder with
+ * @return the UriBuilder
+ */
+ UriBuilder uriString(String uriTemplate);
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
index 09495d3977..9f7694c326 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
@@ -57,7 +57,7 @@ import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
* @see #fromPath(String)
* @see #fromUri(URI)
*/
-public class UriComponentsBuilder implements Cloneable {
+public class UriComponentsBuilder implements UriBuilder, Cloneable {
private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
@@ -360,6 +360,30 @@ public class UriComponentsBuilder implements Cloneable {
return build(false).expand(uriVariableValues);
}
+
+ /**
+ * Build a {@link URI} instance and replaces URI template variables
+ * with the values from an array.
+ * @param uriVariables the map of URI variables
+ * @return the URI
+ */
+ @Override
+ public URI build(Object... uriVariables) {
+ return buildAndExpand(uriVariables).encode().toUri();
+ }
+
+ /**
+ * Build a {@link URI} instance and replaces URI template variables
+ * with the values from a map.
+ * @param uriVariables the map of URI variables
+ * @return the URI
+ */
+ @Override
+ public URI build(Map uriVariables) {
+ return buildAndExpand(uriVariables).encode().toUri();
+ }
+
+
/**
* Build a URI String. This is a shortcut method which combines calls
* to {@link #build()}, then {@link UriComponents#encode()} and finally
@@ -372,10 +396,10 @@ public class UriComponentsBuilder implements Cloneable {
}
- // URI components methods
+ // Instance methods
/**
- * Initialize all components of this URI builder with the components of the given URI.
+ * Initialize components of this builder from components of the given URI.
* @param uri the URI
* @return this UriComponentsBuilder
*/
@@ -411,6 +435,18 @@ public class UriComponentsBuilder implements Cloneable {
return this;
}
+ /**
+ * Initialize components of this {@link UriComponentsBuilder} from the
+ * components of the given {@link UriComponents}.
+ * @param uriComponents the UriComponents instance
+ * @return this UriComponentsBuilder
+ */
+ public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
+ Assert.notNull(uriComponents, "UriComponents must not be null");
+ uriComponents.copyToUriComponentsBuilder(this);
+ return this;
+ }
+
/**
* Set the URI scheme. The given scheme may contain URI template variables,
* and may also be {@code null} to clear the scheme of this builder.
@@ -422,17 +458,6 @@ public class UriComponentsBuilder implements Cloneable {
return this;
}
- /**
- * Set all components of this URI builder from the given {@link UriComponents}.
- * @param uriComponents the UriComponents instance
- * @return this UriComponentsBuilder
- */
- public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
- Assert.notNull(uriComponents, "UriComponents must not be null");
- uriComponents.copyToUriComponentsBuilder(this);
- return this;
- }
-
/**
* Set the URI scheme-specific-part. When invoked, this method overwrites
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java
new file mode 100644
index 0000000000..f7931ffcc0
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.util;
+
+import java.net.URI;
+import java.util.Collections;
+
+import org.junit.Test;
+
+import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Unit tests for {@link DefaultUriBuilderFactory}.
+ * @author Rossen Stoyanchev
+ */
+public class DefaultUriBuilderFactoryTests {
+
+ private static final String baseUrl = "http://foo.com/bar";
+
+
+ @Test
+ public void defaultSettings() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
+ URI uri = factory.uriString("/foo").pathSegment("{id}").build("a/b");
+ assertEquals("/foo/a%2Fb", uri.toString());
+ }
+
+ @Test
+ public void baseUri() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar?id=123");
+ URI uri = factory.uriString("/baz").port(8080).build();
+ assertEquals("http://foo.com:8080/bar/baz?id=123", uri.toString());
+ }
+
+ @Test
+ public void baseUriWithPathOverride() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar");
+ URI uri = factory.uriString("").replacePath("/baz").build();
+ assertEquals("http://foo.com/baz", uri.toString());
+ }
+
+ @Test
+ public void defaultUriVars() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
+ factory.setDefaultUriVariables(Collections.singletonMap("host", "foo.com"));
+ URI uri = factory.uriString("/{id}").build(Collections.singletonMap("id", "123"));
+ assertEquals("http://foo.com/bar/123", uri.toString());
+ }
+
+ @Test
+ public void defaultUriVarsWithOverride() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
+ factory.setDefaultUriVariables(Collections.singletonMap("host", "spring.io"));
+ URI uri = factory.uriString("/baz").build(Collections.singletonMap("host", "docs.spring.io"));
+ assertEquals("http://docs.spring.io/bar/baz", uri.toString());
+ }
+
+ @Test
+ public void defaultUriVarsWithEmptyVarArg() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
+ factory.setDefaultUriVariables(Collections.singletonMap("host", "foo.com"));
+ URI uri = factory.uriString("/baz").build();
+ assertEquals("Expected delegation to build(Map) method", "http://foo.com/bar/baz", uri.toString());
+ }
+
+ @Test
+ public void encodingValuesOnly() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
+ factory.setEncodingMode(EncodingMode.VALUES_ONLY);
+ UriBuilder uriBuilder = factory.uriString("/foo/a%2Fb/{id}");
+
+ String id = "c/d";
+ String expected = "/foo/a%2Fb/c%2Fd";
+
+ assertEquals(expected, uriBuilder.build(id).toString());
+ assertEquals(expected, uriBuilder.build(Collections.singletonMap("id", id)).toString());
+ }
+
+ @Test
+ public void encodingNone() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
+ factory.setEncodingMode(EncodingMode.NONE);
+ UriBuilder uriBuilder = factory.uriString("/foo/a%2Fb/{id}");
+
+ String id = "c%2Fd";
+ String expected = "/foo/a%2Fb/c%2Fd";
+
+ assertEquals(expected, uriBuilder.build(id).toString());
+ assertEquals(expected, uriBuilder.build(Collections.singletonMap("id", id)).toString());
+ }
+
+ @Test
+ public void initialPathSplitIntoPathSegments() throws Exception {
+ DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
+ URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
+ assertEquals("/foo/a%2Fb/baz/c%2Fd", uri.toString());
+ }
+
+}