Skip to content

Commit 6c3a645

Browse files
committed
Move ResolvableType from HttpEntity to PublisherEntity
This commit moves the ResolvableType field from HttpEntity to PublisherEntity, a new entity type defined in MultipartBodyBuilder. With this change, the scope of the ResolvableType is limited to multipart-related code, instead of becoming part of the complete HttpEntity hierarchy. Issue: SPR-16307
1 parent 6e587d5 commit 6c3a645

File tree

4 files changed

+68
-108
lines changed

4 files changed

+68
-108
lines changed

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

-66
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@
1616

1717
package org.springframework.http;
1818

19-
import org.reactivestreams.Publisher;
20-
21-
import org.springframework.core.ParameterizedTypeReference;
22-
import org.springframework.core.ResolvableType;
2319
import org.springframework.lang.Nullable;
24-
import org.springframework.util.Assert;
2520
import org.springframework.util.MultiValueMap;
2621
import org.springframework.util.ObjectUtils;
2722

@@ -72,9 +67,6 @@ public class HttpEntity<T> {
7267
@Nullable
7368
private final T body;
7469

75-
@Nullable
76-
private final ResolvableType bodyType;
77-
7870

7971
/**
8072
* Create a new, empty {@code HttpEntity}.
@@ -105,18 +97,7 @@ public HttpEntity(MultiValueMap<String, String> headers) {
10597
* @param headers the entity headers
10698
*/
10799
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
108-
this(body, null, headers);
109-
}
110-
111-
private HttpEntity(@Nullable T body, @Nullable ResolvableType bodyType,
112-
@Nullable MultiValueMap<String, String> headers) {
113100
this.body = body;
114-
115-
if (bodyType == null && body != null) {
116-
bodyType = ResolvableType.forClass(body.getClass());
117-
}
118-
this.bodyType = bodyType ;
119-
120101
HttpHeaders tempHeaders = new HttpHeaders();
121102
if (headers != null) {
122103
tempHeaders.putAll(headers);
@@ -147,13 +128,6 @@ public boolean hasBody() {
147128
return (this.body != null);
148129
}
149130

150-
/**
151-
* Returns the type of the body.
152-
*/
153-
@Nullable
154-
public ResolvableType getBodyType() {
155-
return this.bodyType;
156-
}
157131

158132
@Override
159133
public boolean equals(@Nullable Object other) {
@@ -185,44 +159,4 @@ public String toString() {
185159
return builder.toString();
186160
}
187161

188-
189-
// Static builder methods
190-
191-
/**
192-
* Create a new {@code HttpEntity} with the given {@link Publisher} as body, class contained in
193-
* {@code publisher}, and headers.
194-
* @param publisher the publisher to use as body
195-
* @param elementClass the class of elements contained in the publisher
196-
* @param headers the entity headers
197-
* @param <S> the type of the elements contained in the publisher
198-
* @param <P> the type of the {@code Publisher}
199-
* @return the created entity
200-
*/
201-
public static <S, P extends Publisher<S>> HttpEntity<P> fromPublisher(P publisher,
202-
Class<S> elementClass, @Nullable MultiValueMap<String, String> headers) {
203-
204-
Assert.notNull(publisher, "'publisher' must not be null");
205-
Assert.notNull(elementClass, "'elementClass' must not be null");
206-
return new HttpEntity<>(publisher, ResolvableType.forClass(elementClass), headers);
207-
}
208-
209-
/**
210-
* Create a new {@code HttpEntity} with the given {@link Publisher} as body, type contained in
211-
* {@code publisher}, and headers.
212-
* @param publisher the publisher to use as body
213-
* @param typeReference the type of elements contained in the publisher
214-
* @param headers the entity headers
215-
* @param <S> the type of the elements contained in the publisher
216-
* @param <P> the type of the {@code Publisher}
217-
* @return the created entity
218-
*/
219-
public static <S, P extends Publisher<S>> HttpEntity<P> fromPublisher(P publisher,
220-
ParameterizedTypeReference<S> typeReference,
221-
@Nullable MultiValueMap<String, String> headers) {
222-
223-
Assert.notNull(publisher, "'publisher' must not be null");
224-
Assert.notNull(typeReference, "'typeReference' must not be null");
225-
return new HttpEntity<>(publisher, ResolvableType.forType(typeReference), headers);
226-
}
227-
228162
}

spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java

+43-29
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ public <T, P extends Publisher<T>> PartBuilder asyncPart(String name, P publishe
143143
Assert.notNull(elementType, "'elementType' must not be null");
144144

145145
HttpHeaders partHeaders = new HttpHeaders();
146-
PublisherClassPartBuilder<T, P> builder =
147-
new PublisherClassPartBuilder<>(publisher, elementClass, partHeaders);
146+
PublisherPartBuilder<T, P> builder =
147+
new PublisherPartBuilder<>(publisher, elementClass, partHeaders);
148148
this.parts.add(name, builder);
149149
return builder;
150150

@@ -155,21 +155,21 @@ public <T, P extends Publisher<T>> PartBuilder asyncPart(String name, P publishe
155155
* the returned {@link PartBuilder}.
156156
* @param name the name of the part to add (may not be empty)
157157
* @param publisher the contents of the part to add
158-
* @param elementType the type of elements contained in the publisher
158+
* @param typeReference the type of elements contained in the publisher
159159
* @return a builder that allows for further header customization
160160
*/
161161
public <T, P extends Publisher<T>> PartBuilder asyncPart(String name, P publisher,
162-
ParameterizedTypeReference<T> elementType) {
162+
ParameterizedTypeReference<T> typeReference) {
163163

164-
Assert.notNull(elementType, "'elementType' must not be null");
165-
ResolvableType elementType1 = ResolvableType.forType(elementType);
164+
Assert.notNull(typeReference, "'typeReference' must not be null");
165+
ResolvableType elementType1 = ResolvableType.forType(typeReference);
166166
Assert.hasLength(name, "'name' must not be empty");
167167
Assert.notNull(publisher, "'publisher' must not be null");
168-
Assert.notNull(elementType1, "'elementType' must not be null");
168+
Assert.notNull(elementType1, "'typeReference' must not be null");
169169

170170
HttpHeaders partHeaders = new HttpHeaders();
171-
PublisherTypReferencePartBuilder<T, P> builder =
172-
new PublisherTypReferencePartBuilder<>(publisher, elementType, partHeaders);
171+
PublisherPartBuilder<T, P> builder =
172+
new PublisherPartBuilder<>(publisher, typeReference, partHeaders);
173173
this.parts.add(name, builder);
174174
return builder;
175175
}
@@ -213,43 +213,57 @@ public HttpEntity<?> build() {
213213
}
214214
}
215215

216-
private static class PublisherClassPartBuilder<S, P extends Publisher<S>>
216+
private static class PublisherPartBuilder<S, P extends Publisher<S>>
217217
extends DefaultPartBuilder {
218218

219-
private final Class<S> bodyType;
219+
private final ResolvableType resolvableType;
220220

221-
public PublisherClassPartBuilder(P body, Class<S> bodyType, HttpHeaders headers) {
221+
public PublisherPartBuilder(P body, Class<S> elementClass, HttpHeaders headers) {
222222
super(body, headers);
223-
this.bodyType = bodyType;
223+
this.resolvableType = ResolvableType.forClass(elementClass);
224+
}
225+
226+
public PublisherPartBuilder(P body, ParameterizedTypeReference<S> typeReference,
227+
HttpHeaders headers) {
228+
229+
super(body, headers);
230+
this.resolvableType = ResolvableType.forType(typeReference);
224231
}
225232

226233
@Override
227234
@SuppressWarnings("unchecked")
228235
public HttpEntity<?> build() {
229-
P body = (P) this.body;
230-
Assert.state(body != null, "'body' must not be null");
231-
return HttpEntity.fromPublisher(body, this.bodyType, this.headers);
236+
P publisher = (P) this.body;
237+
Assert.state(publisher != null, "'publisher' must not be null");
238+
return new PublisherEntity<>(publisher, this.resolvableType, this.headers);
232239
}
233240
}
234241

235-
private static class PublisherTypReferencePartBuilder<S, P extends Publisher<S>>
236-
extends DefaultPartBuilder {
237242

238-
private final ParameterizedTypeReference<S> bodyType;
243+
/**
244+
* Specific subtype of {@link HttpEntity} for containing {@link Publisher}s as body.
245+
* Exposes the type contained in the publisher through {@link #getResolvableType()}.
246+
* @param <T> The type contained in the publisher
247+
* @param <P> The publisher
248+
*/
249+
public static final class PublisherEntity<T, P extends Publisher<T>> extends HttpEntity<P> {
239250

240-
public PublisherTypReferencePartBuilder(P body, ParameterizedTypeReference<S> bodyType,
241-
HttpHeaders headers) {
251+
private final ResolvableType resolvableType;
242252

243-
super(body, headers);
244-
this.bodyType = bodyType;
253+
254+
PublisherEntity(P publisher, ResolvableType resolvableType,
255+
@Nullable MultiValueMap<String, String> headers) {
256+
super(publisher, headers);
257+
Assert.notNull(publisher, "'publisher' must not be null");
258+
Assert.notNull(resolvableType, "'resolvableType' must not be null");
259+
this.resolvableType = resolvableType;
245260
}
246261

247-
@Override
248-
@SuppressWarnings("unchecked")
249-
public HttpEntity<?> build() {
250-
P body = (P) this.body;
251-
Assert.state(body != null, "'body' must not be null");
252-
return HttpEntity.fromPublisher(body, this.bodyType, this.headers);
262+
/**
263+
* Return the resolvable type for this entry.
264+
*/
265+
public ResolvableType getResolvableType() {
266+
return this.resolvableType;
253267
}
254268
}
255269

spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.http.HttpHeaders;
4545
import org.springframework.http.MediaType;
4646
import org.springframework.http.ReactiveHttpOutputMessage;
47+
import org.springframework.http.client.MultipartBodyBuilder;
4748
import org.springframework.http.codec.EncoderHttpMessageWriter;
4849
import org.springframework.http.codec.FormHttpMessageWriter;
4950
import org.springframework.http.codec.HttpMessageWriter;
@@ -230,28 +231,33 @@ private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value) {
230231
MultipartHttpOutputMessage outputMessage = new MultipartHttpOutputMessage(this.bufferFactory, getCharset());
231232

232233
T body;
233-
ResolvableType bodyType = null;
234+
ResolvableType resolvableType = null;
234235
if (value instanceof HttpEntity) {
235236
HttpEntity<T> httpEntity = (HttpEntity<T>) value;
236237
outputMessage.getHeaders().putAll(httpEntity.getHeaders());
237238
body = httpEntity.getBody();
238239
Assert.state(body != null, "MultipartHttpMessageWriter only supports HttpEntity with body");
239-
bodyType = httpEntity.getBodyType();
240+
241+
if (httpEntity instanceof MultipartBodyBuilder.PublisherEntity<?, ?>) {
242+
MultipartBodyBuilder.PublisherEntity<?, ?> publisherEntity =
243+
(MultipartBodyBuilder.PublisherEntity<?, ?>) httpEntity;
244+
resolvableType = publisherEntity.getResolvableType();
245+
}
240246
}
241247
else {
242248
body = value;
243249
}
244250

245-
if (bodyType == null) {
246-
bodyType = ResolvableType.forClass(body.getClass());
251+
if (resolvableType == null) {
252+
resolvableType = ResolvableType.forClass(body.getClass());
247253
}
248254

249255
String filename = (body instanceof Resource ? ((Resource) body).getFilename() : null);
250256
outputMessage.getHeaders().setContentDispositionFormData(name, filename);
251257

252258
MediaType contentType = outputMessage.getHeaders().getContentType();
253259

254-
final ResolvableType finalBodyType = bodyType;
260+
final ResolvableType finalBodyType = resolvableType;
255261
Optional<HttpMessageWriter<?>> writer = this.partWriters.stream()
256262
.filter(partWriter -> partWriter.canWrite(finalBodyType, contentType))
257263
.findFirst();
@@ -264,7 +270,7 @@ private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value) {
264270
body instanceof Publisher ? (Publisher<T>) body : Mono.just(body);
265271

266272
Mono<Void> partWritten = ((HttpMessageWriter<T>) writer.get())
267-
.write(bodyPublisher, bodyType, contentType, outputMessage, Collections.emptyMap());
273+
.write(bodyPublisher, resolvableType, contentType, outputMessage, Collections.emptyMap());
268274

269275
// partWritten.subscribe() is required in order to make sure MultipartHttpOutputMessage#getBody()
270276
// returns a non-null value (occurs with ResourceHttpMessageWriter that invokes

spring-web/src/test/java/org/springframework/http/client/MultipartBodyBuilderTests.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.reactivestreams.Publisher;
2121
import reactor.core.publisher.Flux;
2222

23+
import org.springframework.core.ParameterizedTypeReference;
2324
import org.springframework.core.ResolvableType;
2425
import org.springframework.core.io.ClassPathResource;
2526
import org.springframework.core.io.Resource;
@@ -49,11 +50,12 @@ public void builder() throws Exception {
4950
builder.part("key", multipartData).header("foo", "bar");
5051
builder.part("logo", logo).header("baz", "qux");
5152
builder.part("entity", entity).header("baz", "qux");
52-
builder.asyncPart("publisher", publisher, String.class).header("baz", "qux");
53+
builder.asyncPart("publisherClass", publisher, String.class).header("baz", "qux");
54+
builder.asyncPart("publisherPtr", publisher, new ParameterizedTypeReference<String>() {}).header("baz", "qux");
5355

5456
MultiValueMap<String, HttpEntity<?>> result = builder.build();
5557

56-
assertEquals(4, result.size());
58+
assertEquals(5, result.size());
5759
assertNotNull(result.getFirst("key"));
5860
assertEquals(multipartData, result.getFirst("key").getBody());
5961
assertEquals("bar", result.getFirst("key").getHeaders().getFirst("foo"));
@@ -67,11 +69,15 @@ public void builder() throws Exception {
6769
assertEquals("bar", result.getFirst("entity").getHeaders().getFirst("foo"));
6870
assertEquals("qux", result.getFirst("entity").getHeaders().getFirst("baz"));
6971

70-
assertNotNull(result.getFirst("publisher"));
71-
assertEquals(publisher, result.getFirst("publisher").getBody());
72-
assertEquals(ResolvableType.forClass(String.class), result.getFirst("publisher").getBodyType());
73-
assertEquals("bar", result.getFirst("entity").getHeaders().getFirst("foo"));
74-
assertEquals("qux", result.getFirst("entity").getHeaders().getFirst("baz"));
72+
assertNotNull(result.getFirst("publisherClass"));
73+
assertEquals(publisher, result.getFirst("publisherClass").getBody());
74+
assertEquals(ResolvableType.forClass(String.class), ((MultipartBodyBuilder.PublisherEntity<?,?>) result.getFirst("publisherClass")).getResolvableType());
75+
assertEquals("qux", result.getFirst("publisherClass").getHeaders().getFirst("baz"));
76+
77+
assertNotNull(result.getFirst("publisherPtr"));
78+
assertEquals(publisher, result.getFirst("publisherPtr").getBody());
79+
assertEquals(ResolvableType.forClass(String.class), ((MultipartBodyBuilder.PublisherEntity<?,?>) result.getFirst("publisherPtr")).getResolvableType());
80+
assertEquals("qux", result.getFirst("publisherPtr").getHeaders().getFirst("baz"));
7581
}
7682

7783

0 commit comments

Comments
 (0)