UriComponentsBuilder method to configure URI variables
See Javadoc on UriComponentsBuilder#uriVariables for details. This helps to prepare for SPR-17027 where the MvcUriComponentsBuilder already does a partial expand but was forced to build UriComonents and then create a new UriComponentsBuilder from it to continue. This change makes it possible to stay with the same builder instance. Issue: SPR-17027
This commit is contained in:
@@ -20,6 +20,7 @@ import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
|
||||
import org.springframework.web.util.UriComponents.UriTemplateVariables;
|
||||
|
||||
/**
|
||||
* Builder for {@link UriComponents}.
|
||||
@@ -121,6 +123,8 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
@Nullable
|
||||
private String fragment;
|
||||
|
||||
private final Map<String, Object> uriVariables = new HashMap<>(4);
|
||||
|
||||
private boolean encodeTemplate;
|
||||
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
@@ -381,15 +385,20 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
* @return the URI components
|
||||
*/
|
||||
public UriComponents build(boolean encoded) {
|
||||
UriComponents result;
|
||||
if (this.ssp != null) {
|
||||
return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
|
||||
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
|
||||
}
|
||||
else {
|
||||
HierarchicalUriComponents uriComponents =
|
||||
new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo,
|
||||
this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
|
||||
return (this.encodeTemplate ? uriComponents.encodeTemplate(this.charset) : uriComponents);
|
||||
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment,
|
||||
this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
|
||||
|
||||
result = this.encodeTemplate ? uric.encodeTemplate(this.charset) : uric;
|
||||
}
|
||||
if (!this.uriVariables.isEmpty()) {
|
||||
result = result.expand(name -> this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,7 +442,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
* @see UriComponents#toUriString()
|
||||
*/
|
||||
public String toUriString() {
|
||||
return build().encode().toUriString();
|
||||
return encode().build().toUriString();
|
||||
}
|
||||
|
||||
|
||||
@@ -752,6 +761,25 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure URI variables to be expanded at build time.
|
||||
* <p>The provided variables may be a subset of all required ones. At build
|
||||
* time, the available ones are expanded, while unresolved URI placeholders
|
||||
* are left in place and can still be expanded later.
|
||||
* <p>In contrast to {@link UriComponents#expand(Map)} or
|
||||
* {@link #buildAndExpand(Map)}, this method is useful when you need to
|
||||
* supply URI variables without building the {@link UriComponents} instance
|
||||
* just yet, or perhaps pre-expand some shared default values such as host
|
||||
* and port.
|
||||
* @param uriVariables the URI variables to use
|
||||
* @return this UriComponentsBuilder
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {
|
||||
this.uriVariables.putAll(uriVariables);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt this builder's scheme+host+port from the given headers, specifically
|
||||
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>,
|
||||
|
||||
@@ -70,19 +70,12 @@ public class UriComponentsTests {
|
||||
@Test
|
||||
public void encodeAndExpandPartially() {
|
||||
|
||||
Map<String, Object> uriVars = new HashMap<>();
|
||||
uriVars.put("city", "Z\u00fcrich");
|
||||
|
||||
UriComponents uri = UriComponentsBuilder
|
||||
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode().build()
|
||||
.expand(name -> uriVars.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
|
||||
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode()
|
||||
.uriVariables(Collections.singletonMap("city", "Z\u00fcrich"))
|
||||
.build();
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q={value}", uri.toString());
|
||||
|
||||
uriVars.put("value", "a+b");
|
||||
uri = uri.expand(uriVars);
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.toString());
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.expand("a+b").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -64,7 +64,6 @@ import org.springframework.web.method.support.CompositeUriComponentsContributor;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -421,8 +420,8 @@ public class MvcUriComponentsBuilder {
|
||||
String methodPath = getMethodRequestMapping(method);
|
||||
String path = pathMatcher.combine(typePath, methodPath);
|
||||
baseUrl.path(path);
|
||||
UriComponents uriComponents = applyContributors(baseUrl, method, args);
|
||||
return UriComponentsBuilder.newInstance().uriComponents(uriComponents);
|
||||
|
||||
return applyContributors(baseUrl, method, args);
|
||||
}
|
||||
|
||||
private static UriComponentsBuilder getBaseUrlToUse(@Nullable UriComponentsBuilder baseUrl) {
|
||||
@@ -487,7 +486,7 @@ public class MvcUriComponentsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private static UriComponents applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
|
||||
private static UriComponentsBuilder applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
|
||||
CompositeUriComponentsContributor contributor = getConfiguredUriComponentsContributor();
|
||||
if (contributor == null) {
|
||||
logger.debug("Using default CompositeUriComponentsContributor");
|
||||
@@ -508,8 +507,8 @@ public class MvcUriComponentsBuilder {
|
||||
contributor.contributeMethodArgument(param, args[i], builder, uriVars);
|
||||
}
|
||||
|
||||
// We may not have all URI var values, expand only what we have
|
||||
return builder.build().expand(name -> uriVars.getOrDefault(name, UriComponents.UriTemplateVariables.SKIP_VALUE));
|
||||
// This may not be all the URI variables, supply what we have so far..
|
||||
return builder.uriVariables(uriVars);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -813,7 +812,7 @@ public class MvcUriComponentsBuilder {
|
||||
public MethodArgumentBuilder(@Nullable UriComponentsBuilder baseUrl, Class<?> controllerType, Method method) {
|
||||
Assert.notNull(controllerType, "'controllerType' is required");
|
||||
Assert.notNull(method, "'method' is required");
|
||||
this.baseUrl = (baseUrl != null ? baseUrl : initBaseUrl());
|
||||
this.baseUrl = baseUrl != null ? baseUrl : UriComponentsBuilder.fromPath(getPath());
|
||||
this.controllerType = controllerType;
|
||||
this.method = method;
|
||||
this.argumentValues = new Object[method.getParameterCount()];
|
||||
@@ -822,10 +821,10 @@ public class MvcUriComponentsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private static UriComponentsBuilder initBaseUrl() {
|
||||
private static String getPath() {
|
||||
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
|
||||
String path = builder.build().getPath();
|
||||
return (path != null ? UriComponentsBuilder.fromPath(path) : UriComponentsBuilder.newInstance());
|
||||
return path != null ? path : "";
|
||||
}
|
||||
|
||||
public MethodArgumentBuilder arg(int index, Object value) {
|
||||
@@ -834,13 +833,13 @@ public class MvcUriComponentsBuilder {
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
|
||||
this.argumentValues).build(false).encode().toUriString();
|
||||
return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
|
||||
.build(false).encode().toUriString();
|
||||
}
|
||||
|
||||
public String buildAndExpand(Object... uriVars) {
|
||||
return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
|
||||
this.argumentValues).build(false).expand(uriVars).encode().toString();
|
||||
return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
|
||||
.build(false).expand(uriVars).encode().toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user