Skip to content

Commit 34a0cdf

Browse files
committed
Minor fixes: UriComponentsBuilder, UriComponents, docs
After the latest changes, two small fixes in the clone method to copy the encode flag, and in the encodeUriTemplate method to account for possible null query params. Improvements in the URI encoding section. Issue: SPR-17039, SPR-17027
1 parent 1cd0135 commit 34a0cdf

File tree

4 files changed

+81
-33
lines changed

4 files changed

+81
-33
lines changed

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

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ public int hashCode() {
100100
}
101101
};
102102

103+
private static final MultiValueMap<String, String> EMPTY_QUERY_PARAMS =
104+
CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(0));
105+
103106

104107
@Nullable
105108
private final String userInfo;
@@ -127,22 +130,21 @@ public int hashCode() {
127130
* @param host the host
128131
* @param port the port
129132
* @param path the path
130-
* @param queryParams the query parameters
133+
* @param query the query parameters
131134
* @param fragment the fragment
132135
* @param encoded whether the components are already encoded
133136
*/
134137
HierarchicalUriComponents(@Nullable String scheme, @Nullable String fragment, @Nullable String userInfo,
135138
@Nullable String host, @Nullable String port, @Nullable PathComponent path,
136-
@Nullable MultiValueMap<String, String> queryParams, boolean encoded) {
139+
@Nullable MultiValueMap<String, String> query, boolean encoded) {
137140

138141
super(scheme, fragment);
139142

140143
this.userInfo = userInfo;
141144
this.host = host;
142145
this.port = port;
143-
this.path = (path != null ? path : NULL_PATH_COMPONENT);
144-
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
145-
queryParams != null ? queryParams : new LinkedMultiValueMap<>(0));
146+
this.path = path != null ? path : NULL_PATH_COMPONENT;
147+
this.queryParams = query != null ? CollectionUtils.unmodifiableMultiValueMap(query) : EMPTY_QUERY_PARAMS;
146148
this.encodeState = encoded ? EncodeState.FULLY_ENCODED : EncodeState.RAW;
147149

148150
// Check for illegal characters..
@@ -151,19 +153,18 @@ public int hashCode() {
151153
}
152154
}
153155

154-
private HierarchicalUriComponents(@Nullable String scheme, @Nullable String fragment, @Nullable String userInfo,
155-
@Nullable String host, @Nullable String port, @Nullable PathComponent path,
156-
@Nullable MultiValueMap<String, String> queryParams, EncodeState encodeState,
157-
@Nullable UnaryOperator<String> variableEncoder) {
156+
private HierarchicalUriComponents(@Nullable String scheme, @Nullable String fragment,
157+
@Nullable String userInfo, @Nullable String host, @Nullable String port,
158+
PathComponent path, MultiValueMap<String, String> queryParams,
159+
EncodeState encodeState, @Nullable UnaryOperator<String> variableEncoder) {
158160

159161
super(scheme, fragment);
160162

161163
this.userInfo = userInfo;
162164
this.host = host;
163165
this.port = port;
164-
this.path = (path != null ? path : NULL_PATH_COMPONENT);
165-
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
166-
queryParams != null ? queryParams : new LinkedMultiValueMap<>(0));
166+
this.path = path;
167+
this.queryParams = queryParams;
167168
this.encodeState = encodeState;
168169
this.variableEncoder = variableEncoder;
169170
}
@@ -254,6 +255,11 @@ public MultiValueMap<String, String> getQueryParams() {
254255

255256
// Encoding
256257

258+
/**
259+
* Identical to {@link #encode()} but skipping over URI variable placeholders.
260+
* Also {@link #variableEncoder} is initialized with the given charset for
261+
* use later when URI variables are expanded.
262+
*/
257263
HierarchicalUriComponents encodeTemplate(Charset charset) {
258264
if (this.encodeState.isEncoded()) {
259265
return this;
@@ -268,10 +274,10 @@ HierarchicalUriComponents encodeTemplate(Charset charset) {
268274
String userInfoTo = (getUserInfo() != null ? encoder.apply(getUserInfo(), Type.USER_INFO) : null);
269275
String hostTo = (getHost() != null ? encoder.apply(getHost(), getHostType()) : null);
270276
PathComponent pathTo = this.path.encode(encoder);
271-
MultiValueMap<String, String> paramsTo = encodeQueryParams(encoder);
277+
MultiValueMap<String, String> queryParamsTo = encodeQueryParams(encoder);
272278

273279
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
274-
hostTo, this.port, pathTo, paramsTo, EncodeState.TEMPLATE_ENCODED, this.variableEncoder);
280+
hostTo, this.port, pathTo, queryParamsTo, EncodeState.TEMPLATE_ENCODED, this.variableEncoder);
275281
}
276282

277283
@Override
@@ -287,10 +293,10 @@ public HierarchicalUriComponents encode(Charset charset) {
287293
String hostTo = (this.host != null ? encodeUriComponent(this.host, charset, getHostType()) : null);
288294
BiFunction<String, Type, String> encoder = (s, type) -> encodeUriComponent(s, charset, type);
289295
PathComponent pathTo = this.path.encode(encoder);
290-
MultiValueMap<String, String> paramsTo = encodeQueryParams(encoder);
296+
MultiValueMap<String, String> queryParamsTo = encodeQueryParams(encoder);
291297

292298
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
293-
hostTo, this.port, pathTo, paramsTo, EncodeState.FULLY_ENCODED, null);
299+
hostTo, this.port, pathTo, queryParamsTo, EncodeState.FULLY_ENCODED, null);
294300
}
295301

296302
private MultiValueMap<String, String> encodeQueryParams(BiFunction<String, Type, String> encoder) {
@@ -300,11 +306,11 @@ private MultiValueMap<String, String> encodeQueryParams(BiFunction<String, Type,
300306
String name = encoder.apply(key, Type.QUERY_PARAM);
301307
List<String> encodedValues = new ArrayList<>(values.size());
302308
for (String value : values) {
303-
encodedValues.add(encoder.apply(value, Type.QUERY_PARAM));
309+
encodedValues.add(value != null ? encoder.apply(value, Type.QUERY_PARAM) : null);
304310
}
305311
result.put(name, encodedValues);
306312
});
307-
return result;
313+
return CollectionUtils.unmodifiableMultiValueMap(result);
308314
}
309315

310316
/**
@@ -428,10 +434,10 @@ protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVaria
428434
String hostTo = expandUriComponent(this.host, uriVariables, this.variableEncoder);
429435
String portTo = expandUriComponent(this.port, uriVariables, this.variableEncoder);
430436
PathComponent pathTo = this.path.expand(uriVariables, this.variableEncoder);
431-
MultiValueMap<String, String> paramsTo = expandQueryParams(uriVariables);
437+
MultiValueMap<String, String> queryParamsTo = expandQueryParams(uriVariables);
432438

433439
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
434-
hostTo, portTo, pathTo, paramsTo, this.encodeState, this.variableEncoder);
440+
hostTo, portTo, pathTo, queryParamsTo, this.encodeState, this.variableEncoder);
435441
}
436442

437443
private MultiValueMap<String, String> expandQueryParams(UriTemplateVariables variables) {
@@ -446,7 +452,7 @@ private MultiValueMap<String, String> expandQueryParams(UriTemplateVariables var
446452
}
447453
result.put(name, expandedValues);
448454
});
449-
return result;
455+
return CollectionUtils.unmodifiableMultiValueMap(result);
450456
}
451457

452458
@Override

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ protected UriComponentsBuilder(UriComponentsBuilder other) {
150150
this.pathBuilder = other.pathBuilder.cloneBuilder();
151151
this.queryParams.putAll(other.queryParams);
152152
this.fragment = other.fragment;
153+
this.encodeTemplate = other.encodeTemplate;
154+
this.charset = other.charset;
153155
}
154156

155157

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -738,10 +738,10 @@ public void parsesEmptyUri() {
738738
@Test
739739
public void testClone() {
740740
UriComponentsBuilder builder1 = UriComponentsBuilder.newInstance();
741-
builder1.scheme("http").host("e1.com").path("/p1").pathSegment("ps1").queryParam("q1").fragment("f1");
741+
builder1.scheme("http").host("e1.com").path("/p1").pathSegment("ps1").queryParam("q1").fragment("f1").encode();
742742

743743
UriComponentsBuilder builder2 = (UriComponentsBuilder) builder1.clone();
744-
builder2.scheme("https").host("e2.com").path("p2").pathSegment("ps2").queryParam("q2").fragment("f2");
744+
builder2.scheme("https").host("e2.com").path("p2").pathSegment("{ps2}").queryParam("q2").fragment("f2");
745745

746746
UriComponents result1 = builder1.build();
747747
assertEquals("http", result1.getScheme());
@@ -750,10 +750,10 @@ public void testClone() {
750750
assertEquals("q1", result1.getQuery());
751751
assertEquals("f1", result1.getFragment());
752752

753-
UriComponents result2 = builder2.build();
753+
UriComponents result2 = builder2.buildAndExpand("ps2;a");
754754
assertEquals("https", result2.getScheme());
755755
assertEquals("e2.com", result2.getHost());
756-
assertEquals("/p1/ps1/p2/ps2", result2.getPath());
756+
assertEquals("/p1/ps1/p2/ps2%3Ba", result2.getPath());
757757
assertEquals("q1&q2", result2.getQuery());
758758
assertEquals("f2", result2.getFragment());
759759
}

src/docs/asciidoc/web/web-uris.adoc

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
[source,java,indent=0]
99
[subs="verbatim,quotes"]
1010
----
11-
String uriTemplate = "http://example.com/hotels/{hotel}";
12-
13-
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate) // <1>
11+
UriComponents uriComponents = UriComponentsBuilder
12+
.fromUriString("http://example.com/hotels/{hotel}") // <1>
1413
.queryParam("q", "{q}") // <2>
1514
.encode() // <3>
1615
.build(); // <4>
@@ -23,18 +22,40 @@
2322
<4> Build a `UriComponents`.
2423
<5> Expand variables, and obtain the `URI`.
2524

26-
The above can also be done in shorthand form:
25+
The above can be consolidated into one chain and shortened with `buildAndExpand`:
2726

2827
[source,java,indent=0]
2928
[subs="verbatim,quotes"]
3029
----
31-
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
30+
URI uri = UriComponentsBuilder
31+
.fromUriString("http://example.com/hotels/{hotel}")
3232
.queryParam("q", "{q}")
3333
.encode()
3434
.buildAndExpand("Westin", "123")
3535
.toUri();
3636
----
3737

38+
It can be shortened further by going directly to URI (which implies encoding):
39+
40+
[source,java,indent=0]
41+
[subs="verbatim,quotes"]
42+
----
43+
URI uri = UriComponentsBuilder
44+
.fromUriString("http://example.com/hotels/{hotel}")
45+
.queryParam("q", "{q}")
46+
.build("Westin", "123");
47+
----
48+
49+
Or shorter further yet, with a full URI template:
50+
51+
[source,java,indent=0]
52+
[subs="verbatim,quotes"]
53+
----
54+
URI uri = UriComponentsBuilder
55+
.fromUriString("http://example.com/hotels/{hotel}?q={q}")
56+
.build("Westin", "123");
57+
----
58+
3859

3960
[[web-uribuilder]]
4061
= UriBuilder
@@ -125,15 +146,34 @@ Example usage using option 1:
125146
[source,java,indent=0]
126147
[subs="verbatim,quotes"]
127148
----
128-
UriComponentsBuilder.fromPath("/hotel list/{city}")
149+
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
129150
.queryParam("q", "{q}")
130151
.encode()
131-
.buildAndexpand("New York", "foo+bar")
132-
.toUriString();
152+
.buildAndExpand("New York", "foo+bar")
153+
.toUri();
133154
134155
// Result is "/hotel%20list/New%20York?foo%2Bbar"
135156
----
136157

158+
The above can be shortened by going directly to URI (which implies encoding):
159+
160+
[source,java,indent=0]
161+
[subs="verbatim,quotes"]
162+
----
163+
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
164+
.queryParam("q", "{q}")
165+
.build("New York", "foo+bar")
166+
----
167+
168+
Or shorter further yet, with a full URI template:
169+
170+
[source,java,indent=0]
171+
[subs="verbatim,quotes"]
172+
----
173+
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
174+
.build("New York", "foo+bar")
175+
----
176+
137177
The `WebClient` and the `RestTemplate` expand and encode URI templates internally through
138178
the `UriBuilderFactory` strategy. Both can be configured with a custom strategy:
139179

0 commit comments

Comments
 (0)