Skip to content

Commit fbf88d1

Browse files
committed
Allow to specify hints with the functional web API
The most common use case is specifying JSON views. ServerResponse.BodyBuilder#hint(String, Object) allows to specify response body serialization hints. ServerRequest#body(BodyExtractor, Map) allows to specify request body extraction hints. Issue: SPR-15030
1 parent f51fe5f commit fbf88d1

File tree

17 files changed

+285
-16
lines changed

17 files changed

+285
-16
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.function;
1818

19+
import java.util.Map;
1920
import java.util.function.Supplier;
2021
import java.util.stream.Stream;
2122

@@ -52,6 +53,11 @@ interface Context {
5253
* @return the stream of message readers
5354
*/
5455
Supplier<Stream<HttpMessageReader<?>>> messageReaders();
56+
57+
/**
58+
* Return the map of hints to use to customize body extraction.
59+
*/
60+
Map<String, Object> hints();
5561
}
5662

5763
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static <T> BodyExtractor<Mono<T>, ReactiveHttpInputMessage> toMono(Resolv
6565
Assert.notNull(elementType, "'elementType' must not be null");
6666
return (request, context) -> readWithMessageReaders(request, context,
6767
elementType,
68-
reader -> reader.readMono(elementType, request, Collections.emptyMap()),
68+
reader -> reader.readMono(elementType, request, context.hints()),
6969
Mono::error);
7070
}
7171

@@ -90,7 +90,7 @@ public static <T> BodyExtractor<Flux<T>, ReactiveHttpInputMessage> toFlux(Resolv
9090
Assert.notNull(elementType, "'elementType' must not be null");
9191
return (inputMessage, context) -> readWithMessageReaders(inputMessage, context,
9292
elementType,
93-
reader -> reader.read(elementType, inputMessage, Collections.emptyMap()),
93+
reader -> reader.read(elementType, inputMessage, context.hints()),
9494
Flux::error);
9595
}
9696

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.function;
1818

19+
import java.util.Map;
1920
import java.util.function.Supplier;
2021
import java.util.stream.Stream;
2122

@@ -54,6 +55,11 @@ interface Context {
5455
*/
5556
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
5657

58+
/**
59+
* Return the map of hints to use for response body conversion.
60+
*/
61+
Map<String, Object> hints();
62+
5763
}
5864

5965

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserters.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.web.reactive.function;
1818

19-
import java.util.Collections;
2019
import java.util.List;
2120
import java.util.function.Supplier;
2221
import java.util.stream.Collectors;
@@ -119,7 +118,7 @@ public static <T extends Resource> BodyInserter<T, ReactiveHttpOutputMessage> fr
119118
return (response, context) -> {
120119
HttpMessageWriter<Resource> messageWriter = resourceHttpMessageWriter(context);
121120
return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null,
122-
response, Collections.emptyMap());
121+
response, context.hints());
123122
};
124123
}
125124

@@ -146,7 +145,7 @@ public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S, Serve
146145
return (response, context) -> {
147146
HttpMessageWriter<ServerSentEvent<T>> messageWriter = sseMessageWriter(context);
148147
return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
149-
MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap());
148+
MediaType.TEXT_EVENT_STREAM, response, context.hints());
150149
};
151150
}
152151

@@ -186,7 +185,7 @@ public static <T, S extends Publisher<T>> BodyInserter<S, ServerHttpResponse> fr
186185
return (outputMessage, context) -> {
187186
HttpMessageWriter<T> messageWriter = sseMessageWriter(context);
188187
return messageWriter.write(eventsPublisher, eventType,
189-
MediaType.TEXT_EVENT_STREAM, outputMessage, Collections.emptyMap());
188+
MediaType.TEXT_EVENT_STREAM, outputMessage, context.hints());
190189

191190
};
192191
}
@@ -227,8 +226,7 @@ private static <T, P extends Publisher<?>, M extends ReactiveHttpOutputMessage>
227226
.findFirst()
228227
.map(BodyInserters::cast)
229228
.map(messageWriter -> messageWriter
230-
.write(body, bodyType, contentType, m, Collections
231-
.emptyMap()))
229+
.write(body, bodyType, contentType, m, context.hints()))
232230
.orElseGet(() -> {
233231
List<MediaType> supportedMediaTypes = messageWriters.get()
234232
.flatMap(reader -> reader.getWritableMediaTypes().stream())

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.time.ZonedDateTime;
2323
import java.time.format.DateTimeFormatter;
2424
import java.util.Arrays;
25+
import java.util.Collections;
26+
import java.util.Map;
2527
import java.util.function.Supplier;
2628
import java.util.stream.Stream;
2729

@@ -220,6 +222,11 @@ public Mono<Void> writeTo(ClientHttpRequest request, WebClientStrategies strateg
220222
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
221223
return strategies.messageWriters();
222224
}
225+
226+
@Override
227+
public Map<String, Object> hints() {
228+
return Collections.emptyMap();
229+
}
223230
});
224231
}
225232
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Map;
2122
import java.util.Optional;
2223
import java.util.OptionalLong;
2324
import java.util.function.Function;
@@ -74,6 +75,10 @@ public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
7475
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
7576
return strategies.messageReaders();
7677
}
78+
@Override
79+
public Map<String, Object> hints() {
80+
return Collections.emptyMap();
81+
}
7782
});
7883
}
7984

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,22 @@ public Headers headers() {
7878

7979
@Override
8080
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
81+
return body(extractor, Collections.emptyMap());
82+
}
83+
84+
@Override
85+
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
8186
Assert.notNull(extractor, "'extractor' must not be null");
8287
return extractor.extract(request(),
8388
new BodyExtractor.Context() {
8489
@Override
8590
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
8691
return DefaultServerRequest.this.strategies.messageReaders();
8792
}
93+
@Override
94+
public Map<String, Object> hints() {
95+
return hints;
96+
}
8897
});
8998
}
9099

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.time.format.DateTimeFormatter;
2323
import java.util.Arrays;
2424
import java.util.Collections;
25+
import java.util.HashMap;
2526
import java.util.LinkedHashMap;
2627
import java.util.LinkedHashSet;
2728
import java.util.Locale;
@@ -62,6 +63,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
6263

6364
private final HttpHeaders headers = new HttpHeaders();
6465

66+
private final Map<String, Object> hints = new HashMap<>();
67+
6568

6669
public DefaultServerResponseBuilder(HttpStatus statusCode) {
6770
this.statusCode = statusCode;
@@ -122,6 +125,12 @@ public ServerResponse.BodyBuilder eTag(String eTag) {
122125
return this;
123126
}
124127

128+
@Override
129+
public ServerResponse.BodyBuilder hint(String key, Object value) {
130+
this.hints.put(key, value);
131+
return this;
132+
}
133+
125134
@Override
126135
public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) {
127136
ZonedDateTime gmt = lastModified.withZoneSameInstant(ZoneId.of("GMT"));
@@ -182,7 +191,7 @@ public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
182191
public <T> Mono<ServerResponse> body(BodyInserter<T, ? super ServerHttpResponse> inserter) {
183192
Assert.notNull(inserter, "'inserter' must not be null");
184193
return Mono
185-
.just(new BodyInserterServerResponse<T>(this.statusCode, this.headers, inserter));
194+
.just(new BodyInserterServerResponse<T>(this.statusCode, this.headers, inserter, this.hints));
186195
}
187196

188197
@Override
@@ -276,11 +285,14 @@ private static final class BodyInserterServerResponse<T> extends AbstractServerR
276285

277286
private final BodyInserter<T, ? super ServerHttpResponse> inserter;
278287

288+
private final Map<String, Object> hints;
289+
279290
public BodyInserterServerResponse(HttpStatus statusCode, HttpHeaders headers,
280-
BodyInserter<T, ? super ServerHttpResponse> inserter) {
291+
BodyInserter<T, ? super ServerHttpResponse> inserter, Map<String, Object> hints) {
281292

282293
super(statusCode, headers);
283294
this.inserter = inserter;
295+
this.hints = hints;
284296
}
285297

286298
@Override
@@ -292,6 +304,10 @@ public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategi
292304
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
293305
return strategies.messageWriters();
294306
}
307+
@Override
308+
public Map<String, Object> hints() {
309+
return hints;
310+
}
295311
});
296312
}
297313
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
323323
return this.request.body(extractor);
324324
}
325325

326+
@Override
327+
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
328+
return this.request.body(extractor, hints);
329+
}
330+
326331
@Override
327332
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
328333
return this.request.bodyToMono(elementClass);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.http.HttpMethod;
3232
import org.springframework.http.HttpRange;
3333
import org.springframework.http.MediaType;
34+
import org.springframework.http.codec.json.AbstractJackson2Codec;
3435
import org.springframework.http.server.reactive.ServerHttpRequest;
3536
import org.springframework.web.reactive.function.BodyExtractor;
3637

@@ -40,6 +41,7 @@
4041
* {@link #body(BodyExtractor)} respectively.
4142
*
4243
* @author Arjen Poutsma
44+
* @author Sebastien Deleuze
4345
* @since 5.0
4446
*/
4547
public interface ServerRequest {
@@ -71,9 +73,20 @@ default String path() {
7173
* @param extractor the {@code BodyExtractor} that reads from the request
7274
* @param <T> the type of the body returned
7375
* @return the extracted body
76+
* @see #body(BodyExtractor, Map)
7477
*/
7578
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);
7679

80+
/**
81+
* Extract the body with the given {@code BodyExtractor} and hints.
82+
* @param extractor the {@code BodyExtractor} that reads from the request
83+
* @param hints the map of hints like {@link AbstractJackson2Codec#JSON_VIEW_HINT}
84+
* to use to customize body extraction
85+
* @param <T> the type of the body returned
86+
* @return the extracted body
87+
*/
88+
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints);
89+
7790
/**
7891
* Extract the body to a {@code Mono}.
7992
* @param elementClass the class of element in the {@code Mono}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.http.HttpMethod;
3232
import org.springframework.http.HttpStatus;
3333
import org.springframework.http.MediaType;
34+
import org.springframework.http.codec.json.AbstractJackson2Codec;
3435
import org.springframework.http.server.reactive.ServerHttpResponse;
3536
import org.springframework.util.Assert;
3637
import org.springframework.web.reactive.function.BodyInserter;
@@ -42,6 +43,7 @@
4243
* {@linkplain HandlerFunction handler function} or {@linkplain HandlerFilterFunction filter function}.
4344
*
4445
* @author Arjen Poutsma
46+
* @author Sebastien Deleuze
4547
* @since 5.0
4648
*/
4749
public interface ServerResponse {
@@ -312,6 +314,12 @@ interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
312314
*/
313315
BodyBuilder contentType(MediaType contentType);
314316

317+
/**
318+
* Add a serialization hint like {@link AbstractJackson2Codec#JSON_VIEW_HINT} to
319+
* customize how the body will be serialized.
320+
*/
321+
BodyBuilder hint(String key, Object value);
322+
315323
/**
316324
* Set the body of the response to the given {@code Publisher} and return it. This
317325
* convenience method combines {@link #body(BodyInserter)} and

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
9191
return this.request.body(extractor);
9292
}
9393

94+
@Override
95+
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
96+
return this.request.body(extractor, hints);
97+
}
98+
9499
@Override
95100
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
96101
return this.request.bodyToMono(elementClass);

0 commit comments

Comments
 (0)