Skip to content

Commit 04bbc2a

Browse files
committed
Merge pull request #24406 from parviz/93-22083-uri-template
Closes gh-24406
2 parents d170846 + f28e08e commit 04bbc2a

File tree

4 files changed

+184
-38
lines changed

4 files changed

+184
-38
lines changed

spring-web/src/main/java/org/springframework/http/RequestEntity.java

Lines changed: 160 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,39 +22,32 @@
2222
import java.time.Instant;
2323
import java.time.ZonedDateTime;
2424
import java.util.Arrays;
25+
import java.util.Map;
2526
import java.util.function.Consumer;
27+
import java.util.function.Function;
2628

2729
import org.springframework.lang.Nullable;
2830
import org.springframework.util.MultiValueMap;
2931
import org.springframework.util.ObjectUtils;
32+
import org.springframework.web.util.DefaultUriBuilderFactory;
33+
import org.springframework.web.util.UriTemplateHandler;
3034

3135
/**
32-
* Extension of {@link HttpEntity} that adds a {@linkplain HttpMethod method} and
33-
* {@linkplain URI uri}. Used in {@code RestTemplate} and {@code @Controller} methods.
36+
* Extension of {@link HttpEntity} that also exposes the HTTP method and the
37+
* target URL. For use in the {@code RestTemplate} to prepare requests with
38+
* and in {@code @Controller} methods to represent request input.
3439
*
35-
* <p>In {@code RestTemplate}, this class is used as parameter in
36-
* {@link org.springframework.web.client.RestTemplate#exchange(RequestEntity, Class) exchange()}:
40+
* <p>Example use with the {@code RestTemplate}:
3741
* <pre class="code">
3842
* MyRequest body = ...
3943
* RequestEntity&lt;MyRequest&gt; request = RequestEntity
40-
* .post(new URI(&quot;https://example.com/bar&quot;))
44+
* .post(&quot;https://example.com/{foo}&quot;, &quot;bar&quot;)
4145
* .accept(MediaType.APPLICATION_JSON)
4246
* .body(body);
4347
* ResponseEntity&lt;MyResponse&gt; response = template.exchange(request, MyResponse.class);
4448
* </pre>
4549
*
46-
* <p>If you would like to provide a URI template with variables, consider using
47-
* {@link org.springframework.web.util.DefaultUriBuilderFactory DefaultUriBuilderFactory}:
48-
* <pre class="code">
49-
* // Create shared factory
50-
* UriBuilderFactory factory = new DefaultUriBuilderFactory();
51-
*
52-
* // Use factory to create URL from template
53-
* URI uri = factory.uriString(&quot;https://example.com/{foo}&quot;).build(&quot;bar&quot;);
54-
* RequestEntity&lt;MyRequest&gt; request = RequestEntity.post(uri).accept(MediaType.APPLICATION_JSON).body(body);
55-
* </pre>
56-
*
57-
* <p>Can also be used in Spring MVC, as a parameter in a @Controller method:
50+
* <p>Example use in an {@code @Controller}:
5851
* <pre class="code">
5952
* &#64;RequestMapping("/handle")
6053
* public void handle(RequestEntity&lt;String&gt; request) {
@@ -66,17 +59,20 @@
6659
*
6760
* @author Arjen Poutsma
6861
* @author Sebastien Deleuze
62+
* @author Parviz Rozikov
6963
* @since 4.1
7064
* @param <T> the body type
7165
* @see #getMethod()
7266
* @see #getUrl()
7367
*/
7468
public class RequestEntity<T> extends HttpEntity<T> {
7569

70+
private final static UriTemplateHandler DEFAULT_TEMPLATE_HANDLER = new DefaultUriBuilderFactory();
71+
7672
@Nullable
7773
private final HttpMethod method;
7874

79-
private final URI url;
75+
private final Function<UriTemplateHandler, URI> uriFunction;
8076

8177
@Nullable
8278
private final Type type;
@@ -148,9 +144,19 @@ public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> h
148144
public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers,
149145
@Nullable HttpMethod method, URI url, @Nullable Type type) {
150146

147+
this(body, headers, method, handler -> url, type);
148+
}
149+
150+
/**
151+
* Private constructor with URI function.
152+
* @since 5.3
153+
*/
154+
private RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers,
155+
@Nullable HttpMethod method, Function<UriTemplateHandler, URI> uriFunction, @Nullable Type type) {
156+
151157
super(body, headers);
152158
this.method = method;
153-
this.url = url;
159+
this.uriFunction = uriFunction;
154160
this.type = type;
155161
}
156162

@@ -166,12 +172,27 @@ public HttpMethod getMethod() {
166172

167173
/**
168174
* Return the URL of the request.
175+
* <p>If the URL was provided as a URI template, the returned URI is expanded
176+
* and encoded with {@link DefaultUriBuilderFactory}.
169177
* @return the URL as a {@code URI}
170178
*/
171179
public URI getUrl() {
172-
return this.url;
180+
return this.uriFunction.apply(DEFAULT_TEMPLATE_HANDLER);
173181
}
174182

183+
/**
184+
* Return the URL of the request.
185+
* <p>If the URL was provided as a URI template, the returned URI is expanded
186+
* with the given {@link DefaultUriBuilderFactory}.
187+
* @param templateHandler the handler to use to expand the URI template with
188+
* @return the URL as a {@code URI}
189+
* @since 5.3
190+
*/
191+
public URI getUrl(UriTemplateHandler templateHandler) {
192+
return this.uriFunction.apply(templateHandler);
193+
}
194+
195+
175196
/**
176197
* Return the type of the request's body.
177198
* @return the request's body type, or {@code null} if not known
@@ -206,7 +227,7 @@ public boolean equals(@Nullable Object other) {
206227
public int hashCode() {
207228
int hashCode = super.hashCode();
208229
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.method);
209-
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.url);
230+
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getUrl());
210231
return hashCode;
211232
}
212233

@@ -241,6 +262,30 @@ public static BodyBuilder method(HttpMethod method, URI url) {
241262
return new DefaultBodyBuilder(method, url);
242263
}
243264

265+
/**
266+
* Create a builder with the given HTTP method, URI template, and variables.
267+
* @param method the HTTP method (GET, POST, etc)
268+
* @param uriTemplate the uri template to use
269+
* @param uriVariables variables to expand the URI template with
270+
* @return the created builder
271+
* @since 5.3
272+
*/
273+
public static BodyBuilder method(HttpMethod method, String uriTemplate, Object... uriVariables) {
274+
return new DefaultBodyBuilder(method, uriTemplate, uriVariables);
275+
}
276+
277+
/**
278+
* Create a builder with the given HTTP method, URI template, and variables.
279+
* @param method the HTTP method (GET, POST, etc)
280+
* @param uriTemplate the uri template to use
281+
* @return the created builder
282+
* @since 5.3
283+
*/
284+
public static BodyBuilder method(HttpMethod method, String uriTemplate, Map<String, ?> uriVariables) {
285+
return new DefaultBodyBuilder(method, uriTemplate, uriVariables);
286+
}
287+
288+
244289
/**
245290
* Create an HTTP GET builder with the given url.
246291
* @param url the URL
@@ -250,6 +295,17 @@ public static HeadersBuilder<?> get(URI url) {
250295
return method(HttpMethod.GET, url);
251296
}
252297

298+
/**
299+
* Create an HTTP GET builder with the given string base uri template.
300+
* @param uriTemplate the uri template to use
301+
* @param uriVariables variables to expand the URI template with
302+
* @return the created builder
303+
* @since 5.3
304+
*/
305+
public static HeadersBuilder<?> get(String uriTemplate, Object... uriVariables) {
306+
return method(HttpMethod.GET, uriTemplate, uriVariables);
307+
}
308+
253309
/**
254310
* Create an HTTP HEAD builder with the given url.
255311
* @param url the URL
@@ -259,6 +315,17 @@ public static HeadersBuilder<?> head(URI url) {
259315
return method(HttpMethod.HEAD, url);
260316
}
261317

318+
/**
319+
* Create an HTTP HEAD builder with the given string base uri template.
320+
* @param uriTemplate the uri template to use
321+
* @param uriVariables variables to expand the URI template with
322+
* @return the created builder
323+
* @since 5.3
324+
*/
325+
public static HeadersBuilder<?> head(String uriTemplate, Object... uriVariables) {
326+
return method(HttpMethod.HEAD, uriTemplate, uriVariables);
327+
}
328+
262329
/**
263330
* Create an HTTP POST builder with the given url.
264331
* @param url the URL
@@ -268,6 +335,17 @@ public static BodyBuilder post(URI url) {
268335
return method(HttpMethod.POST, url);
269336
}
270337

338+
/**
339+
* Create an HTTP POST builder with the given string base uri template.
340+
* @param uriTemplate the uri template to use
341+
* @param uriVariables variables to expand the URI template with
342+
* @return the created builder
343+
* @since 5.3
344+
*/
345+
public static BodyBuilder post(String uriTemplate, Object... uriVariables) {
346+
return method(HttpMethod.POST, uriTemplate, uriVariables);
347+
}
348+
271349
/**
272350
* Create an HTTP PUT builder with the given url.
273351
* @param url the URL
@@ -277,6 +355,17 @@ public static BodyBuilder put(URI url) {
277355
return method(HttpMethod.PUT, url);
278356
}
279357

358+
/**
359+
* Create an HTTP PUT builder with the given string base uri template.
360+
* @param uriTemplate the uri template to use
361+
* @param uriVariables variables to expand the URI template with
362+
* @return the created builder
363+
* @since 5.3
364+
*/
365+
public static BodyBuilder put(String uriTemplate, Object... uriVariables) {
366+
return method(HttpMethod.PUT, uriTemplate, uriVariables);
367+
}
368+
280369
/**
281370
* Create an HTTP PATCH builder with the given url.
282371
* @param url the URL
@@ -286,6 +375,17 @@ public static BodyBuilder patch(URI url) {
286375
return method(HttpMethod.PATCH, url);
287376
}
288377

378+
/**
379+
* Create an HTTP PATCH builder with the given string base uri template.
380+
* @param uriTemplate the uri template to use
381+
* @param uriVariables variables to expand the URI template with
382+
* @return the created builder
383+
* @since 5.3
384+
*/
385+
public static BodyBuilder patch(String uriTemplate, Object... uriVariables) {
386+
return method(HttpMethod.PATCH, uriTemplate, uriVariables);
387+
}
388+
289389
/**
290390
* Create an HTTP DELETE builder with the given url.
291391
* @param url the URL
@@ -295,6 +395,17 @@ public static HeadersBuilder<?> delete(URI url) {
295395
return method(HttpMethod.DELETE, url);
296396
}
297397

398+
/**
399+
* Create an HTTP DELETE builder with the given string base uri template.
400+
* @param uriTemplate the uri template to use
401+
* @param uriVariables variables to expand the URI template with
402+
* @return the created builder
403+
* @since 5.3
404+
*/
405+
public static HeadersBuilder<?> delete(String uriTemplate, Object... uriVariables) {
406+
return method(HttpMethod.DELETE, uriTemplate, uriVariables);
407+
}
408+
298409
/**
299410
* Creates an HTTP OPTIONS builder with the given url.
300411
* @param url the URL
@@ -304,6 +415,17 @@ public static HeadersBuilder<?> options(URI url) {
304415
return method(HttpMethod.OPTIONS, url);
305416
}
306417

418+
/**
419+
* Creates an HTTP OPTIONS builder with the given string base uri template.
420+
* @param uriTemplate the uri template to use
421+
* @param uriVariables variables to expand the URI template with
422+
* @return the created builder
423+
* @since 5.3
424+
*/
425+
public static HeadersBuilder<?> options(String uriTemplate, Object... uriVariables) {
426+
return method(HttpMethod.OPTIONS, uriTemplate, uriVariables);
427+
}
428+
307429

308430
/**
309431
* Defines a builder that adds headers to the request entity.
@@ -439,13 +561,24 @@ private static class DefaultBodyBuilder implements BodyBuilder {
439561

440562
private final HttpMethod method;
441563

442-
private final URI url;
564+
private final Function<UriTemplateHandler, URI> uriFunction;
443565

444566
private final HttpHeaders headers = new HttpHeaders();
445567

568+
446569
public DefaultBodyBuilder(HttpMethod method, URI url) {
447570
this.method = method;
448-
this.url = url;
571+
this.uriFunction = handler -> url;
572+
}
573+
574+
public DefaultBodyBuilder(HttpMethod method, String uriTemplate, Object... uriVars) {
575+
this.method = method;
576+
this.uriFunction = handler -> handler.expand(uriTemplate, uriVars);
577+
}
578+
579+
public DefaultBodyBuilder(HttpMethod method, String uriTemplate, Map<String, ?> uriVars) {
580+
this.method = method;
581+
this.uriFunction = handler -> handler.expand(uriTemplate, uriVars);
449582
}
450583

451584
@Override
@@ -520,18 +653,17 @@ public BodyBuilder ifNoneMatch(String... ifNoneMatches) {
520653

521654
@Override
522655
public RequestEntity<Void> build() {
523-
return new RequestEntity<>(this.headers, this.method, this.url);
656+
return new RequestEntity<>(null, this.headers, this.method, this.uriFunction, null);
524657
}
525658

526659
@Override
527660
public <T> RequestEntity<T> body(T body) {
528-
return new RequestEntity<>(body, this.headers, this.method, this.url);
661+
return new RequestEntity<>(body, this.headers, this.method, this.uriFunction, null);
529662
}
530663

531664
@Override
532665
public <T> RequestEntity<T> body(T body, Type type) {
533-
return new RequestEntity<>(body, this.headers, this.method, this.url, type);
666+
return new RequestEntity<>(body, this.headers, this.method, this.uriFunction, type);
534667
}
535668
}
536-
537669
}

spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,8 @@ public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> r
638638

639639
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
640640
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
641-
return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor));
641+
URI url = requestEntity.getUrl(this.uriTemplateHandler);
642+
return nonNull(doExecute(url, requestEntity.getMethod(), requestCallback, responseExtractor));
642643
}
643644

644645
@Override
@@ -648,7 +649,8 @@ public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Parameteri
648649
Type type = responseType.getType();
649650
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
650651
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
651-
return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor));
652+
URI url = requestEntity.getUrl(this.uriTemplateHandler);
653+
return nonNull(doExecute(url, requestEntity.getMethod(), requestCallback, responseExtractor));
652654
}
653655

654656

0 commit comments

Comments
 (0)