Skip to content

Commit 49e5c4d

Browse files
committed
Polish MultipartBodyBuilder
Improve Javadoc Consistently reject Publisher unless using asyncPart Consistently set Content-Type when specified
1 parent 6eac141 commit 49e5c4d

File tree

1 file changed

+71
-37
lines changed

1 file changed

+71
-37
lines changed

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

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,39 @@
3636
import org.springframework.util.MultiValueMap;
3737

3838
/**
39-
* A mutable builder for multipart form bodies. For example:
39+
* Builder for the body of a multipart request, producing
40+
* {@code MultiValueMap<String, HttpEntity>}, which can be provided to the
41+
* {@code WebClient} through the {@code syncBody} method.
42+
*
43+
* Examples:
4044
* <pre class="code">
4145
*
46+
* // Add form field
4247
* MultipartBodyBuilder builder = new MultipartBodyBuilder();
43-
* builder.part("form field", "form value");
48+
* builder.part("form field", "form value").header("foo", "bar");
4449
*
50+
* // Add file part
4551
* Resource image = new ClassPathResource("image.jpg");
46-
* builder.part("image", image).header("Baz", "Qux");
52+
* builder.part("image", image).header("foo", "bar");
53+
*
54+
* // Add content (e.g. JSON)
55+
* Account account = ...
56+
* builder.part("account", account).header("foo", "bar");
57+
*
58+
* // Add content from Publisher
59+
* Mono&lt;Account&gt; accountMono = ...
60+
* builder.asyncPart("account", accountMono).header("foo", "bar");
4761
*
62+
* // Build and use
4863
* MultiValueMap&lt;String, HttpEntity&lt;?&gt;&gt; multipartBody = builder.build();
49-
* // use multipartBody with RestTemplate or WebClient
64+
*
65+
* Mono&lt;Void&gt; result = webClient.post()
66+
* .uri("...")
67+
* .syncBody(multipartBody)
68+
* .retrieve()
69+
* .bodyToMono(Void.class)
5070
* </pre>
51-
71+
*
5272
* @author Arjen Poutsma
5373
* @author Rossen Stoyanchev
5474
* @since 5.0.2
@@ -67,7 +87,14 @@ public MultipartBodyBuilder() {
6787

6888

6989
/**
70-
* Add a part from an Object.
90+
* Add a part where the Object may be:
91+
* <ul>
92+
* <li>String -- form field
93+
* <li>{@link org.springframework.core.io.Resource Resource} -- file part
94+
* <li>Object -- content to be encoded (e.g. to JSON)
95+
* <li>HttpEntity -- part content and headers although generally it's
96+
* easier to add headers through the returned builder</li>
97+
* </ul>
7198
* @param name the name of the part to add
7299
* @param part the part data
73100
* @return builder that allows for further customization of part headers
@@ -77,51 +104,53 @@ public PartBuilder part(String name, Object part) {
77104
}
78105

79106
/**
80-
* Variant of {@link #part(String, Object)} that also accepts a MediaType
81-
* which is used to determine how to encode the part.
107+
* Variant of {@link #part(String, Object)} that also accepts a MediaType.
82108
* @param name the name of the part to add
83109
* @param part the part data
84-
* @param contentType the media type for the part
110+
* @param contentType the media type to help with encoding the part
85111
* @return builder that allows for further customization of part headers
86112
*/
87113
public PartBuilder part(String name, Object part, @Nullable MediaType contentType) {
88114
Assert.hasLength(name, "'name' must not be empty");
89115
Assert.notNull(part, "'part' must not be null");
90116

91-
if (part instanceof Publisher) {
92-
throw new IllegalArgumentException("Use publisher(String, Publisher, Class) or " +
93-
"publisher(String, Publisher, ParameterizedTypeReference) for adding Publisher parts");
94-
}
95-
96117
if (part instanceof PublisherEntity<?,?>) {
97118
PublisherPartBuilder<?, ?> builder = new PublisherPartBuilder<>((PublisherEntity<?, ?>) part);
119+
if (contentType != null) {
120+
builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString());
121+
}
98122
this.parts.add(name, builder);
99123
return builder;
100124
}
101125

102126
Object partBody;
103-
HttpHeaders partHeaders = new HttpHeaders();
104-
127+
HttpHeaders partHeaders = null;
105128
if (part instanceof HttpEntity) {
106-
HttpEntity<?> httpEntity = (HttpEntity<?>) part;
107-
partBody = httpEntity.getBody();
108-
partHeaders.addAll(httpEntity.getHeaders());
129+
partBody = ((HttpEntity<?>) part).getBody();
130+
partHeaders = new HttpHeaders();
131+
partHeaders.putAll(((HttpEntity<?>) part).getHeaders());
109132
}
110133
else {
111134
partBody = part;
112135
}
113136

114-
if (contentType != null) {
115-
partHeaders.setContentType(contentType);
137+
if (partBody instanceof Publisher) {
138+
throw new IllegalArgumentException(
139+
"Use asyncPart(String, Publisher, Class)" +
140+
" or asyncPart(String, Publisher, ParameterizedTypeReference) or" +
141+
" or MultipartBodyBuilder.PublisherEntity");
116142
}
117143

118144
DefaultPartBuilder builder = new DefaultPartBuilder(partHeaders, partBody);
145+
if (contentType != null) {
146+
builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString());
147+
}
119148
this.parts.add(name, builder);
120149
return builder;
121150
}
122151

123152
/**
124-
* Add an asynchronous part with {@link Publisher}-based content.
153+
* Add a part from {@link Publisher} content.
125154
* @param name the name of the part to add
126155
* @param publisher the part contents
127156
* @param elementClass the type of elements contained in the publisher
@@ -132,17 +161,15 @@ public <T, P extends Publisher<T>> PartBuilder asyncPart(String name, P publishe
132161
Assert.notNull(publisher, "'publisher' must not be null");
133162
Assert.notNull(elementClass, "'elementClass' must not be null");
134163

135-
HttpHeaders headers = new HttpHeaders();
136-
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(headers, publisher, elementClass);
164+
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(null, publisher, elementClass);
137165
this.parts.add(name, builder);
138166
return builder;
139167

140168
}
141169

142170
/**
143-
* Variant of {@link #asyncPart(String, Publisher, Class)} that accepts a
144-
* {@link ParameterizedTypeReference} for the element type, which allows
145-
* specifying generic type information.
171+
* Variant of {@link #asyncPart(String, Publisher, Class)} with a
172+
* {@link ParameterizedTypeReference} for the element type information.
146173
* @param name the name of the part to add
147174
* @param publisher the part contents
148175
* @param typeReference the type of elements contained in the publisher
@@ -155,8 +182,7 @@ public <T, P extends Publisher<T>> PartBuilder asyncPart(
155182
Assert.notNull(publisher, "'publisher' must not be null");
156183
Assert.notNull(typeReference, "'typeReference' must not be null");
157184

158-
HttpHeaders headers = new HttpHeaders();
159-
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(headers, publisher, typeReference);
185+
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(null, publisher, typeReference);
160186
this.parts.add(name, builder);
161187
return builder;
162188
}
@@ -201,28 +227,36 @@ public interface PartBuilder {
201227

202228
private static class DefaultPartBuilder implements PartBuilder {
203229

204-
protected final HttpHeaders headers;
230+
@Nullable
231+
protected HttpHeaders headers;
205232

206233
@Nullable
207234
protected final Object body;
208235

209-
public DefaultPartBuilder(HttpHeaders headers, @Nullable Object body) {
236+
public DefaultPartBuilder(@Nullable HttpHeaders headers, @Nullable Object body) {
210237
this.headers = headers;
211238
this.body = body;
212239
}
213240

214241
@Override
215242
public PartBuilder header(String headerName, String... headerValues) {
216-
this.headers.addAll(headerName, Arrays.asList(headerValues));
243+
initHeadersIfNecessary().addAll(headerName, Arrays.asList(headerValues));
217244
return this;
218245
}
219246

220247
@Override
221248
public PartBuilder headers(Consumer<HttpHeaders> headersConsumer) {
222-
headersConsumer.accept(this.headers);
249+
headersConsumer.accept(initHeadersIfNecessary());
223250
return this;
224251
}
225252

253+
private HttpHeaders initHeadersIfNecessary() {
254+
if (this.headers == null) {
255+
this.headers = new HttpHeaders();
256+
}
257+
return this.headers;
258+
}
259+
226260
public HttpEntity<?> build() {
227261
return new HttpEntity<>(this.body, this.headers);
228262
}
@@ -233,14 +267,14 @@ private static class PublisherPartBuilder<S, P extends Publisher<S>> extends Def
233267

234268
private final ResolvableType resolvableType;
235269

236-
public PublisherPartBuilder(HttpHeaders headers, P body, Class<S> elementClass) {
270+
public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, Class<S> elementClass) {
237271
super(headers, body);
238272
this.resolvableType = ResolvableType.forClass(elementClass);
239273
}
240274

241-
public PublisherPartBuilder(HttpHeaders headers, P body, ParameterizedTypeReference<S> typeReference) {
275+
public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, ParameterizedTypeReference<S> typeRef) {
242276
super(headers, body);
243-
this.resolvableType = ResolvableType.forType(typeReference);
277+
this.resolvableType = ResolvableType.forType(typeRef);
244278
}
245279

246280
public PublisherPartBuilder(PublisherEntity<S, P> other) {
@@ -265,7 +299,7 @@ public HttpEntity<?> build() {
265299
* @param <T> the type contained in the publisher
266300
* @param <P> the publisher
267301
*/
268-
public static final class PublisherEntity<T, P extends Publisher<T>> extends HttpEntity<P>
302+
static final class PublisherEntity<T, P extends Publisher<T>> extends HttpEntity<P>
269303
implements ResolvableTypeProvider {
270304

271305
private final ResolvableType resolvableType;

0 commit comments

Comments
 (0)