Skip to content

Commit f1c55a3

Browse files
committed
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
1 parent 34a0cdf commit f1c55a3

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
2222
import java.util.ArrayList;
23+
import java.util.HashMap;
2324
import java.util.LinkedList;
2425
import java.util.List;
2526
import java.util.Map;
@@ -35,6 +36,7 @@
3536
import org.springframework.util.ObjectUtils;
3637
import org.springframework.util.StringUtils;
3738
import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
39+
import org.springframework.web.util.UriComponents.UriTemplateVariables;
3840

3941
/**
4042
* Builder for {@link UriComponents}.
@@ -121,6 +123,8 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
121123
@Nullable
122124
private String fragment;
123125

126+
private final Map<String, Object> uriVariables = new HashMap<>(4);
127+
124128
private boolean encodeTemplate;
125129

126130
private Charset charset = StandardCharsets.UTF_8;
@@ -381,15 +385,20 @@ public UriComponents build() {
381385
* @return the URI components
382386
*/
383387
public UriComponents build(boolean encoded) {
388+
UriComponents result;
384389
if (this.ssp != null) {
385-
return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
390+
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
386391
}
387392
else {
388-
HierarchicalUriComponents uriComponents =
389-
new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo,
390-
this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
391-
return (this.encodeTemplate ? uriComponents.encodeTemplate(this.charset) : uriComponents);
393+
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment,
394+
this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
395+
396+
result = this.encodeTemplate ? uric.encodeTemplate(this.charset) : uric;
397+
}
398+
if (!this.uriVariables.isEmpty()) {
399+
result = result.expand(name -> this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
392400
}
401+
return result;
393402
}
394403

395404
/**
@@ -433,7 +442,7 @@ public URI build(Map<String, ?> uriVariables) {
433442
* @see UriComponents#toUriString()
434443
*/
435444
public String toUriString() {
436-
return build().encode().toUriString();
445+
return encode().build().toUriString();
437446
}
438447

439448

@@ -752,6 +761,25 @@ public UriComponentsBuilder fragment(@Nullable String fragment) {
752761
return this;
753762
}
754763

764+
/**
765+
* Configure URI variables to be expanded at build time.
766+
* <p>The provided variables may be a subset of all required ones. At build
767+
* time, the available ones are expanded, while unresolved URI placeholders
768+
* are left in place and can still be expanded later.
769+
* <p>In contrast to {@link UriComponents#expand(Map)} or
770+
* {@link #buildAndExpand(Map)}, this method is useful when you need to
771+
* supply URI variables without building the {@link UriComponents} instance
772+
* just yet, or perhaps pre-expand some shared default values such as host
773+
* and port.
774+
* @param uriVariables the URI variables to use
775+
* @return this UriComponentsBuilder
776+
* @since 5.0.8
777+
*/
778+
public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {
779+
this.uriVariables.putAll(uriVariables);
780+
return this;
781+
}
782+
755783
/**
756784
* Adapt this builder's scheme+host+port from the given headers, specifically
757785
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>,

spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,12 @@ public void encodeAndExpand() {
7070
@Test
7171
public void encodeAndExpandPartially() {
7272

73-
Map<String, Object> uriVars = new HashMap<>();
74-
uriVars.put("city", "Z\u00fcrich");
75-
7673
UriComponents uri = UriComponentsBuilder
77-
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode().build()
78-
.expand(name -> uriVars.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
79-
80-
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q={value}", uri.toString());
74+
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode()
75+
.uriVariables(Collections.singletonMap("city", "Z\u00fcrich"))
76+
.build();
8177

82-
uriVars.put("value", "a+b");
83-
uri = uri.expand(uriVars);
84-
85-
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.toString());
78+
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.expand("a+b").toString());
8679
}
8780

8881
@Test

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
import org.springframework.web.servlet.DispatcherServlet;
6565
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
6666
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
67-
import org.springframework.web.util.UriComponents;
6867
import org.springframework.web.util.UriComponentsBuilder;
6968

7069
/**
@@ -421,8 +420,8 @@ private static UriComponentsBuilder fromMethodInternal(@Nullable UriComponentsBu
421420
String methodPath = getMethodRequestMapping(method);
422421
String path = pathMatcher.combine(typePath, methodPath);
423422
baseUrl.path(path);
424-
UriComponents uriComponents = applyContributors(baseUrl, method, args);
425-
return UriComponentsBuilder.newInstance().uriComponents(uriComponents);
423+
424+
return applyContributors(baseUrl, method, args);
426425
}
427426

428427
private static UriComponentsBuilder getBaseUrlToUse(@Nullable UriComponentsBuilder baseUrl) {
@@ -487,7 +486,7 @@ else if (methods.size() > 1) {
487486
}
488487
}
489488

490-
private static UriComponents applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
489+
private static UriComponentsBuilder applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
491490
CompositeUriComponentsContributor contributor = getConfiguredUriComponentsContributor();
492491
if (contributor == null) {
493492
logger.debug("Using default CompositeUriComponentsContributor");
@@ -508,8 +507,8 @@ private static UriComponents applyContributors(UriComponentsBuilder builder, Met
508507
contributor.contributeMethodArgument(param, args[i], builder, uriVars);
509508
}
510509

511-
// We may not have all URI var values, expand only what we have
512-
return builder.build().expand(name -> uriVars.getOrDefault(name, UriComponents.UriTemplateVariables.SKIP_VALUE));
510+
// This may not be all the URI variables, supply what we have so far..
511+
return builder.uriVariables(uriVars);
513512
}
514513

515514
@Nullable
@@ -813,7 +812,7 @@ public MethodArgumentBuilder(Class<?> controllerType, Method method) {
813812
public MethodArgumentBuilder(@Nullable UriComponentsBuilder baseUrl, Class<?> controllerType, Method method) {
814813
Assert.notNull(controllerType, "'controllerType' is required");
815814
Assert.notNull(method, "'method' is required");
816-
this.baseUrl = (baseUrl != null ? baseUrl : initBaseUrl());
815+
this.baseUrl = baseUrl != null ? baseUrl : UriComponentsBuilder.fromPath(getPath());
817816
this.controllerType = controllerType;
818817
this.method = method;
819818
this.argumentValues = new Object[method.getParameterCount()];
@@ -822,10 +821,10 @@ public MethodArgumentBuilder(@Nullable UriComponentsBuilder baseUrl, Class<?> co
822821
}
823822
}
824823

825-
private static UriComponentsBuilder initBaseUrl() {
824+
private static String getPath() {
826825
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
827826
String path = builder.build().getPath();
828-
return (path != null ? UriComponentsBuilder.fromPath(path) : UriComponentsBuilder.newInstance());
827+
return path != null ? path : "";
829828
}
830829

831830
public MethodArgumentBuilder arg(int index, Object value) {
@@ -834,13 +833,13 @@ public MethodArgumentBuilder arg(int index, Object value) {
834833
}
835834

836835
public String build() {
837-
return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
838-
this.argumentValues).build(false).encode().toUriString();
836+
return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
837+
.build(false).encode().toUriString();
839838
}
840839

841840
public String buildAndExpand(Object... uriVars) {
842-
return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
843-
this.argumentValues).build(false).expand(uriVars).encode().toString();
841+
return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
842+
.build(false).expand(uriVars).encode().toString();
844843
}
845844
}
846845

0 commit comments

Comments
 (0)