Skip to content

Commit eb435f5

Browse files
committed
Add ParameterizedTypeReference method variants to ServerRequest/ServerResponse
This commit changes adds overloaded `ParameterizedTypeReference ` variants to body-related methods in `ServerRequest` and `ServerResponse`. It also adds a single PTR variant to ClientRequest, which was missing before. Issue: SPR-15817
1 parent c5fc400 commit eb435f5

File tree

12 files changed

+226
-6
lines changed

12 files changed

+226
-6
lines changed

spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import reactor.core.publisher.Flux;
3434
import reactor.core.publisher.Mono;
3535

36+
import org.springframework.core.ParameterizedTypeReference;
3637
import org.springframework.http.HttpCookie;
3738
import org.springframework.http.HttpHeaders;
3839
import org.springframework.http.HttpMethod;
@@ -149,13 +150,27 @@ public <S> Mono<S> bodyToMono(Class<? extends S> elementClass) {
149150
return (Mono<S>) this.body;
150151
}
151152

153+
@Override
154+
@SuppressWarnings("unchecked")
155+
public <S> Mono<S> bodyToMono(ParameterizedTypeReference<S> typeReference) {
156+
Assert.state(this.body != null, "No body");
157+
return (Mono<S>) this.body;
158+
}
159+
152160
@Override
153161
@SuppressWarnings("unchecked")
154162
public <S> Flux<S> bodyToFlux(Class<? extends S> elementClass) {
155163
Assert.state(this.body != null, "No body");
156164
return (Flux<S>) this.body;
157165
}
158166

167+
@Override
168+
@SuppressWarnings("unchecked")
169+
public <S> Flux<S> bodyToFlux(ParameterizedTypeReference<S> typeReference) {
170+
Assert.state(this.body != null, "No body");
171+
return (Flux<S>) this.body;
172+
}
173+
159174
@Override
160175
public Map<String, Object> attributes() {
161176
return this.attributes;

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.reactivestreams.Publisher;
2525
import reactor.core.publisher.Mono;
2626

27+
import org.springframework.core.ParameterizedTypeReference;
2728
import org.springframework.http.HttpHeaders;
2829
import org.springframework.http.HttpMethod;
2930
import org.springframework.http.client.reactive.ClientHttpRequest;
@@ -189,6 +190,17 @@ interface Builder {
189190
*/
190191
<S, P extends Publisher<S>> Builder body(P publisher, Class<S> elementClass);
191192

193+
/**
194+
* Set the body of the request to the given {@code Publisher} and return it.
195+
* @param publisher the {@code Publisher} to write to the request
196+
* @param typeReference a type reference describing the elements contained in the publisher
197+
* @param <S> the type of the elements contained in the publisher
198+
* @param <P> the type of the {@code Publisher}
199+
* @return the built request
200+
*/
201+
<S, P extends Publisher<S>> Builder body(P publisher,
202+
ParameterizedTypeReference<S> typeReference);
203+
192204
/**
193205
* Set the attribute with the given name to the given value.
194206
* @param name the name of the attribute to add

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.reactivestreams.Publisher;
2828
import reactor.core.publisher.Mono;
2929

30+
import org.springframework.core.ParameterizedTypeReference;
3031
import org.springframework.http.HttpCookie;
3132
import org.springframework.http.HttpHeaders;
3233
import org.springframework.http.HttpMethod;
@@ -106,6 +107,17 @@ public <S, P extends Publisher<S>> ClientRequest.Builder body(P publisher, Class
106107
return this;
107108
}
108109

110+
@Override
111+
public <S, P extends Publisher<S>> ClientRequest.Builder body(P publisher,
112+
ParameterizedTypeReference<S> typeReference) {
113+
114+
Assert.notNull(publisher, "'publisher' must not be null");
115+
Assert.notNull(typeReference, "'typeReference' must not be null");
116+
117+
this.inserter = BodyInserters.fromPublisher(publisher, typeReference);
118+
return this;
119+
}
120+
109121
@Override
110122
public ClientRequest.Builder attribute(String name, Object value) {
111123
this.attributes.put(name, value);

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import reactor.core.publisher.Flux;
3333
import reactor.core.publisher.Mono;
3434

35+
import org.springframework.core.ParameterizedTypeReference;
3536
import org.springframework.http.HttpCookie;
3637
import org.springframework.http.HttpHeaders;
3738
import org.springframework.http.HttpMethod;
@@ -139,12 +140,24 @@ public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
139140
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
140141
}
141142

143+
@Override
144+
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
145+
Mono<T> mono = body(BodyExtractors.toMono(typeReference));
146+
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
147+
}
148+
142149
@Override
143150
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
144151
Flux<T> flux = body(BodyExtractors.toFlux(elementClass));
145152
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
146153
}
147154

155+
@Override
156+
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
157+
Flux<T> flux = body(BodyExtractors.toFlux(typeReference));
158+
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
159+
}
160+
148161
@Override
149162
public Map<String, Object> attributes() {
150163
return this.exchange.getAttributes();

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
package org.springframework.web.reactive.function.server;
1818

1919
import java.net.URI;
20-
import java.time.ZoneId;
2120
import java.time.ZonedDateTime;
22-
import java.time.format.DateTimeFormatter;
2321
import java.util.Arrays;
2422
import java.util.HashMap;
2523
import java.util.LinkedHashSet;
@@ -33,6 +31,7 @@
3331
import org.reactivestreams.Publisher;
3432
import reactor.core.publisher.Mono;
3533

34+
import org.springframework.core.ParameterizedTypeReference;
3635
import org.springframework.http.CacheControl;
3736
import org.springframework.http.HttpHeaders;
3837
import org.springframework.http.HttpMethod;
@@ -183,6 +182,21 @@ public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<
183182
.map(entityResponse -> entityResponse);
184183
}
185184

185+
@Override
186+
public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
187+
ParameterizedTypeReference<T> typeReference) {
188+
189+
Assert.notNull(publisher, "'publisher' must not be null");
190+
Assert.notNull(typeReference, "'typeReference' must not be null");
191+
192+
return new DefaultEntityResponseBuilder<>(publisher,
193+
BodyInserters.fromPublisher(publisher, typeReference))
194+
.headers(this.headers)
195+
.status(this.statusCode)
196+
.build()
197+
.map(entityResponse -> entityResponse);
198+
}
199+
186200
@Override
187201
public Mono<ServerResponse> syncBody(Object body) {
188202
Assert.notNull(body, "'body' must not be null");

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import reactor.core.publisher.Flux;
3636
import reactor.core.publisher.Mono;
3737

38+
import org.springframework.core.ParameterizedTypeReference;
3839
import org.springframework.http.HttpCookie;
3940
import org.springframework.http.HttpMethod;
4041
import org.springframework.http.MediaType;
@@ -515,11 +516,21 @@ public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
515516
return this.request.bodyToMono(elementClass);
516517
}
517518

519+
@Override
520+
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
521+
return this.request.bodyToMono(typeReference);
522+
}
523+
518524
@Override
519525
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
520526
return this.request.bodyToFlux(elementClass);
521527
}
522528

529+
@Override
530+
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
531+
return this.request.bodyToFlux(typeReference);
532+
}
533+
523534
@Override
524535
public Optional<Object> attribute(String name) {
525536
return this.request.attribute(name);

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import reactor.core.publisher.Flux;
3030
import reactor.core.publisher.Mono;
3131

32+
import org.springframework.core.ParameterizedTypeReference;
3233
import org.springframework.http.HttpCookie;
3334
import org.springframework.http.HttpHeaders;
3435
import org.springframework.http.HttpMethod;
@@ -118,6 +119,14 @@ default PathContainer pathContainer() {
118119
*/
119120
<T> Mono<T> bodyToMono(Class<? extends T> elementClass);
120121

122+
/**
123+
* Extract the body to a {@code Mono}.
124+
* @param typeReference a type reference describing the expected response request type
125+
* @param <T> the element type
126+
* @return a mono containing the body of the given type {@code T}
127+
*/
128+
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference);
129+
121130
/**
122131
* Extract the body to a {@code Flux}.
123132
* @param elementClass the class of element in the {@code Flux}
@@ -126,6 +135,14 @@ default PathContainer pathContainer() {
126135
*/
127136
<T> Flux<T> bodyToFlux(Class<? extends T> elementClass);
128137

138+
/**
139+
* Extract the body to a {@code Flux}.
140+
* @param typeReference a type reference describing the expected request body type
141+
* @param <T> the element type
142+
* @return a flux containing the body of the given type {@code T}
143+
*/
144+
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference);
145+
129146
/**
130147
* Return the request attribute value if present.
131148
* @param name the attribute name

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.reactivestreams.Publisher;
2929
import reactor.core.publisher.Mono;
3030

31+
import org.springframework.core.ParameterizedTypeReference;
3132
import org.springframework.http.CacheControl;
3233
import org.springframework.http.HttpHeaders;
3334
import org.springframework.http.HttpMethod;
@@ -352,6 +353,19 @@ interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
352353
*/
353354
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> elementClass);
354355

356+
/**
357+
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
358+
* This convenience method combines {@link #body(BodyInserter)} and
359+
* {@link BodyInserters#fromPublisher(Publisher, Class)}.
360+
* @param publisher the {@code Publisher} to write to the response
361+
* @param typeReference a type reference describing the elements contained in the publisher
362+
* @param <T> the type of the elements contained in the publisher
363+
* @param <P> the type of the {@code Publisher}
364+
* @return the built response
365+
*/
366+
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
367+
ParameterizedTypeReference<T> typeReference);
368+
355369
/**
356370
* Set the body of the response to the given synchronous {@code Object} and return it.
357371
* This convenience method combines {@link #body(BodyInserter)} and

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import reactor.core.publisher.Flux;
3030
import reactor.core.publisher.Mono;
3131

32+
import org.springframework.core.ParameterizedTypeReference;
3233
import org.springframework.http.HttpCookie;
3334
import org.springframework.http.HttpHeaders;
3435
import org.springframework.http.HttpMethod;
@@ -118,11 +119,21 @@ public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
118119
return this.delegate.bodyToMono(elementClass);
119120
}
120121

122+
@Override
123+
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
124+
return this.delegate.bodyToMono(typeReference);
125+
}
126+
121127
@Override
122128
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
123129
return this.delegate.bodyToFlux(elementClass);
124130
}
125131

132+
@Override
133+
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
134+
return this.delegate.bodyToFlux(typeReference);
135+
}
136+
126137
@Override
127138
public Optional<Object> attribute(String name) {
128139
return this.delegate.attribute(name);

spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
package org.springframework.web.reactive.function.client;
1818

1919
import java.net.URI;
20-
import java.nio.ByteBuffer;
2120
import java.util.ArrayList;
2221
import java.util.List;
2322

2423
import org.junit.Test;
24+
import org.reactivestreams.Publisher;
2525
import reactor.core.publisher.Mono;
2626
import reactor.test.StepVerifier;
2727

28+
import org.springframework.core.ParameterizedTypeReference;
2829
import org.springframework.core.codec.CharSequenceEncoder;
2930
import org.springframework.core.io.buffer.DataBuffer;
3031
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
@@ -101,8 +102,7 @@ public void bodyInserter() throws Exception {
101102
BodyInserter<String, ClientHttpRequest> inserter =
102103
(response, strategies) -> {
103104
byte[] bodyBytes = body.getBytes(UTF_8);
104-
ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes);
105-
DataBuffer buffer = new DefaultDataBufferFactory().wrap(byteBuffer);
105+
DataBuffer buffer = new DefaultDataBufferFactory().wrap(bodyBytes);
106106

107107
return response.writeWith(Mono.just(buffer));
108108
};
@@ -119,6 +119,55 @@ public void bodyInserter() throws Exception {
119119
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
120120
result.writeTo(request, strategies).block();
121121
assertNotNull(request.getBody());
122+
123+
StepVerifier.create(request.getBody())
124+
.expectNextCount(1)
125+
.verifyComplete();
126+
}
127+
128+
@Test
129+
public void bodyClass() throws Exception {
130+
String body = "foo";
131+
Publisher<String> publisher = Mono.just(body);
132+
ClientRequest result = ClientRequest.method(POST, URI.create("http://example.com"))
133+
.body(publisher, String.class).build();
134+
135+
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
136+
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
137+
138+
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
139+
when(strategies.messageWriters()).thenReturn(messageWriters);
140+
141+
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
142+
result.writeTo(request, strategies).block();
143+
assertNotNull(request.getBody());
144+
145+
StepVerifier.create(request.getBody())
146+
.expectNextCount(1)
147+
.verifyComplete();
148+
}
149+
150+
@Test
151+
public void bodyParameterizedTypeReference() throws Exception {
152+
String body = "foo";
153+
Publisher<String> publisher = Mono.just(body);
154+
ParameterizedTypeReference<String> typeReference = new ParameterizedTypeReference<String>() {};
155+
ClientRequest result = ClientRequest.method(POST, URI.create("http://example.com"))
156+
.body(publisher, typeReference).build();
157+
158+
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
159+
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
160+
161+
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
162+
when(strategies.messageWriters()).thenReturn(messageWriters);
163+
164+
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
165+
result.writeTo(request, strategies).block();
166+
assertNotNull(request.getBody());
167+
168+
StepVerifier.create(request.getBody())
169+
.expectNextCount(1)
170+
.verifyComplete();
122171
}
123172

124173
}

0 commit comments

Comments
 (0)