Skip to content

Commit e74c59b

Browse files
committed
Introduce ServerHttpMessageWriter/Reader to resolve hints
Issue: SPR-14693
1 parent 084daa7 commit e74c59b

File tree

6 files changed

+307
-12
lines changed

6 files changed

+307
-12
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.lang.annotation.Annotation;
1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Map;
2122
import java.util.function.Function;
2223
import java.util.stream.Collectors;
2324

@@ -32,6 +33,7 @@
3233
import org.springframework.core.annotation.AnnotationUtils;
3334
import org.springframework.http.MediaType;
3435
import org.springframework.http.codec.HttpMessageReader;
36+
import org.springframework.http.codec.ServerHttpMessageReader;
3537
import org.springframework.http.server.reactive.ServerHttpRequest;
3638
import org.springframework.util.Assert;
3739
import org.springframework.util.ObjectUtils;
@@ -115,8 +117,8 @@ public ReactiveAdapterRegistry getAdapterRegistry() {
115117
protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyRequired,
116118
ServerWebExchange exchange) {
117119

118-
Class<?> bodyType = ResolvableType.forMethodParameter(bodyParameter).resolve();
119-
ReactiveAdapter adapter = getAdapterRegistry().getAdapterTo(bodyType);
120+
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
121+
ReactiveAdapter adapter = getAdapterRegistry().getAdapterTo(bodyType.resolve());
120122

121123
ResolvableType elementType = ResolvableType.forMethodParameter(bodyParameter);
122124
if (adapter != null) {
@@ -130,9 +132,15 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
130132
}
131133

132134
for (HttpMessageReader<?> reader : getMessageReaders()) {
133-
if (reader.canRead(elementType, mediaType, Collections.emptyMap())) {
135+
136+
Map<String, Object> hints = (reader instanceof ServerHttpMessageReader ?
137+
((ServerHttpMessageReader<?>)reader).resolveReadHints(bodyType, elementType,
138+
mediaType, exchange.getRequest()) : Collections.emptyMap());
139+
140+
if (reader.canRead(elementType, mediaType, hints)) {
141+
134142
if (adapter != null && adapter.getDescriptor().isMultiValue()) {
135-
Flux<?> flux = reader.read(elementType, request, Collections.emptyMap())
143+
Flux<?> flux = reader.read(elementType, request, hints)
136144
.onErrorResumeWith(ex -> Flux.error(getReadError(ex, bodyParameter)));
137145
if (checkRequired(adapter, isBodyRequired)) {
138146
flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter)));
@@ -143,7 +151,7 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
143151
return Mono.just(adapter.fromPublisher(flux));
144152
}
145153
else {
146-
Mono<?> mono = reader.readMono(elementType, request, Collections.emptyMap())
154+
Mono<?> mono = reader.readMono(elementType, request, hints)
147155
.otherwise(ex -> Mono.error(getReadError(ex, bodyParameter)));
148156
if (checkRequired(adapter, isBodyRequired)) {
149157
mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter)));

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Map;
2021
import java.util.stream.Collectors;
2122

2223
import org.reactivestreams.Publisher;
@@ -28,6 +29,7 @@
2829
import org.springframework.core.ResolvableType;
2930
import org.springframework.http.MediaType;
3031
import org.springframework.http.codec.HttpMessageWriter;
32+
import org.springframework.http.codec.ServerHttpMessageWriter;
3133
import org.springframework.http.server.reactive.ServerHttpResponse;
3234
import org.springframework.util.Assert;
3335
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
@@ -89,22 +91,22 @@ public List<HttpMessageWriter<?>> getMessageWriters() {
8991

9092

9193
@SuppressWarnings("unchecked")
92-
protected Mono<Void> writeBody(Object body, MethodParameter bodyType, ServerWebExchange exchange) {
94+
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
9395

94-
Class<?> bodyClass = bodyType.getParameterType();
95-
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(bodyClass, body);
96+
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
97+
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(bodyType.resolve(), body);
9698

9799
Publisher<?> publisher;
98100
ResolvableType elementType;
99101
if (adapter != null) {
100102
publisher = adapter.toPublisher(body);
101103
elementType = adapter.getDescriptor().isNoValue() ?
102104
ResolvableType.forClass(Void.class) :
103-
ResolvableType.forMethodParameter(bodyType).getGeneric(0);
105+
bodyType.getGeneric(0);
104106
}
105107
else {
106108
publisher = Mono.justOrEmpty(body);
107-
elementType = ResolvableType.forMethodParameter(bodyType);
109+
elementType = bodyType;
108110
}
109111

110112
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
@@ -121,10 +123,14 @@ protected Mono<Void> writeBody(Object body, MethodParameter bodyType, ServerWebE
121123

122124
if (bestMediaType != null) {
123125
for (HttpMessageWriter<?> messageWriter : getMessageWriters()) {
124-
if (messageWriter.canWrite(elementType, bestMediaType, Collections.emptyMap())) {
126+
Map<String, Object> hints = (messageWriter instanceof ServerHttpMessageWriter ?
127+
((ServerHttpMessageWriter<?>)messageWriter).resolveWriteHints(bodyType, elementType,
128+
bestMediaType, exchange.getRequest()) : Collections.emptyMap());
129+
if (messageWriter.canWrite(elementType, bestMediaType, hints)) {
130+
125131
ServerHttpResponse response = exchange.getResponse();
126132
return messageWriter.write((Publisher) publisher, elementType,
127-
bestMediaType, response, Collections.emptyMap());
133+
bestMediaType, response, hints);
128134
}
129135
}
130136
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.codec;
18+
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import reactor.core.publisher.Flux;
24+
import reactor.core.publisher.Mono;
25+
26+
import org.springframework.core.MethodParameter;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.http.MediaType;
29+
import org.springframework.http.ReactiveHttpInputMessage;
30+
import org.springframework.http.server.reactive.ServerHttpRequest;
31+
32+
/**
33+
* {@link HttpMessageReader} wrapper to extend that implements {@link ServerHttpMessageReader} in order
34+
* to allow providing hints.
35+
*
36+
* @author Sebastien Deleuze
37+
* @since 5.0
38+
*/
39+
public abstract class AbstractServerHttpMessageReader<T> implements ServerHttpMessageReader<T> {
40+
41+
private HttpMessageReader<T> reader;
42+
43+
44+
public AbstractServerHttpMessageReader(HttpMessageReader<T> reader) {
45+
this.reader = reader;
46+
}
47+
48+
@Override
49+
public boolean canRead(ResolvableType elementType, MediaType mediaType, Map<String, Object> hints) {
50+
return this.reader.canRead(elementType, mediaType, hints);
51+
}
52+
53+
@Override
54+
public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
55+
return this.reader.read(elementType, inputMessage, hints);
56+
}
57+
58+
@Override
59+
public Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
60+
return this.reader.readMono(elementType, inputMessage, hints);
61+
}
62+
63+
@Override
64+
public List<MediaType> getReadableMediaTypes() {
65+
return this.reader.getReadableMediaTypes();
66+
}
67+
68+
@Override
69+
public final Map<String, Object> resolveReadHints(ResolvableType streamType,
70+
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
71+
72+
Map<String, Object> hints = new HashMap<>();
73+
if (this.reader instanceof ServerHttpMessageReader) {
74+
hints.putAll(((ServerHttpMessageReader<T>)this.reader).resolveReadHints(streamType, elementType, mediaType, request));
75+
}
76+
hints.putAll(resolveReadHintsInternal(streamType, elementType, mediaType, request));
77+
return hints;
78+
}
79+
80+
/**
81+
* Abstract method that returns hints which can be used to customize how the body should be read.
82+
* Invoked from {@link #resolveReadHints}.
83+
* @param streamType the original type used in the method parameter. For annotation
84+
* based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}.
85+
* @param elementType the stream element type to return
86+
* @param mediaType the media type to read, can be {@code null} if not specified.
87+
* Typically the value of a {@code Content-Type} header.
88+
* @param request the current HTTP request
89+
* @return Additional information about how to read the body
90+
*/
91+
protected abstract Map<String, Object> resolveReadHintsInternal(ResolvableType streamType,
92+
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request);
93+
94+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.codec;
18+
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.reactivestreams.Publisher;
24+
import reactor.core.publisher.Mono;
25+
26+
import org.springframework.core.MethodParameter;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.http.MediaType;
29+
import org.springframework.http.ReactiveHttpOutputMessage;
30+
import org.springframework.http.server.reactive.ServerHttpRequest;
31+
32+
/**
33+
* {@link HttpMessageWriter} wrapper to extend that implements {@link ServerHttpMessageWriter} in order
34+
* to allow providing hints.
35+
*
36+
* @author Sebastien Deleuze
37+
* @since 5.0
38+
*/
39+
public abstract class AbstractServerHttpMessageWriter<T> implements ServerHttpMessageWriter<T> {
40+
41+
private HttpMessageWriter<T> writer;
42+
43+
44+
public AbstractServerHttpMessageWriter(HttpMessageWriter<T> writer) {
45+
this.writer = writer;
46+
}
47+
48+
@Override
49+
public boolean canWrite(ResolvableType elementType, MediaType mediaType, Map<String, Object> hints) {
50+
return this.writer.canWrite(elementType, mediaType, hints);
51+
}
52+
53+
@Override
54+
public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType,
55+
MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
56+
57+
return this.writer.write(inputStream, elementType, mediaType, outputMessage, hints);
58+
}
59+
60+
@Override
61+
public List<MediaType> getWritableMediaTypes() {
62+
return this.writer.getWritableMediaTypes();
63+
}
64+
65+
@Override
66+
public final Map<String, Object> resolveWriteHints(ResolvableType streamType,
67+
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
68+
69+
Map<String, Object> hints = new HashMap<>();
70+
if (this.writer instanceof ServerHttpMessageWriter) {
71+
hints.putAll(((ServerHttpMessageWriter<T>)this.writer).resolveWriteHints(streamType, elementType, mediaType, request));
72+
}
73+
hints.putAll(resolveWriteHintsInternal(streamType, elementType, mediaType, request));
74+
return hints;
75+
}
76+
77+
/**
78+
* Abstract method that returns hints which can be used to customize how the body should be written.
79+
* Invoked from {@link #resolveWriteHints}.
80+
* @param streamType the original type used for the method return value. For annotation
81+
* based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}.
82+
* @param elementType the stream element type to process
83+
* @param mediaType the content type to use when writing. May be {@code null} to
84+
* indicate that the default content type of the converter must be used.
85+
* @param request the current HTTP request
86+
* @return Additional information about how to write the body
87+
*/
88+
protected abstract Map<String, Object> resolveWriteHintsInternal(ResolvableType streamType,
89+
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request);
90+
91+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.codec;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.core.ResolvableType;
23+
import org.springframework.http.MediaType;
24+
import org.springframework.http.server.reactive.ServerHttpRequest;
25+
26+
/**
27+
* Server and annotation based controller specific {@link HttpMessageReader} that allows to
28+
* resolve hints using annotations or request based information.
29+
*
30+
* @author Sebastien Deleuze
31+
* @since 5.0
32+
*/
33+
public interface ServerHttpMessageReader<T> extends HttpMessageReader<T> {
34+
35+
/**
36+
* Return hints that can be used to customize how the body should be read
37+
* @param streamType the original type used in the method parameter. For annotation
38+
* based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}.
39+
* @param elementType the stream element type to return
40+
* @param mediaType the media type to read, can be {@code null} if not specified.
41+
* Typically the value of a {@code Content-Type} header.
42+
* @param request the current HTTP request
43+
* @return Additional information about how to read the body
44+
*/
45+
Map<String, Object> resolveReadHints(ResolvableType streamType, ResolvableType elementType,
46+
MediaType mediaType, ServerHttpRequest request);
47+
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.codec;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.core.ResolvableType;
23+
import org.springframework.http.MediaType;
24+
import org.springframework.http.server.reactive.ServerHttpRequest;
25+
26+
/**
27+
* Server oriented {@link HttpMessageWriter} that allows to resolve hints using annotations or
28+
* request based information.
29+
*
30+
* @author Sebastien Deleuze
31+
* @since 5.0
32+
*/
33+
public interface ServerHttpMessageWriter<T> extends HttpMessageWriter<T> {
34+
35+
/**
36+
* Return hints that can be used to customize how the body should be written
37+
* @param streamType the original type used for the method return value. For annotation
38+
* based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}.
39+
* @param elementType the stream element type to process
40+
* @param mediaType the content type to use when writing. May be {@code null} to
41+
* indicate that the default content type of the converter must be used.
42+
* @param request the current HTTP request
43+
* @return Additional information about how to write the body
44+
*/
45+
Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
46+
MediaType mediaType, ServerHttpRequest request);
47+
48+
}

0 commit comments

Comments
 (0)