36
36
import org .springframework .util .MultiValueMap ;
37
37
38
38
/**
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:
40
44
* <pre class="code">
41
45
*
46
+ * // Add form field
42
47
* MultipartBodyBuilder builder = new MultipartBodyBuilder();
43
- * builder.part("form field", "form value");
48
+ * builder.part("form field", "form value").header("foo", "bar") ;
44
49
*
50
+ * // Add file part
45
51
* 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<Account> accountMono = ...
60
+ * builder.asyncPart("account", accountMono).header("foo", "bar");
47
61
*
62
+ * // Build and use
48
63
* MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
49
- * // use multipartBody with RestTemplate or WebClient
64
+ *
65
+ * Mono<Void> result = webClient.post()
66
+ * .uri("...")
67
+ * .syncBody(multipartBody)
68
+ * .retrieve()
69
+ * .bodyToMono(Void.class)
50
70
* </pre>
51
-
71
+ *
52
72
* @author Arjen Poutsma
53
73
* @author Rossen Stoyanchev
54
74
* @since 5.0.2
@@ -67,7 +87,14 @@ public MultipartBodyBuilder() {
67
87
68
88
69
89
/**
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>
71
98
* @param name the name of the part to add
72
99
* @param part the part data
73
100
* @return builder that allows for further customization of part headers
@@ -77,51 +104,53 @@ public PartBuilder part(String name, Object part) {
77
104
}
78
105
79
106
/**
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.
82
108
* @param name the name of the part to add
83
109
* @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
85
111
* @return builder that allows for further customization of part headers
86
112
*/
87
113
public PartBuilder part (String name , Object part , @ Nullable MediaType contentType ) {
88
114
Assert .hasLength (name , "'name' must not be empty" );
89
115
Assert .notNull (part , "'part' must not be null" );
90
116
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
-
96
117
if (part instanceof PublisherEntity <?,?>) {
97
118
PublisherPartBuilder <?, ?> builder = new PublisherPartBuilder <>((PublisherEntity <?, ?>) part );
119
+ if (contentType != null ) {
120
+ builder .header (HttpHeaders .CONTENT_TYPE , contentType .toString ());
121
+ }
98
122
this .parts .add (name , builder );
99
123
return builder ;
100
124
}
101
125
102
126
Object partBody ;
103
- HttpHeaders partHeaders = new HttpHeaders ();
104
-
127
+ HttpHeaders partHeaders = null ;
105
128
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 ());
109
132
}
110
133
else {
111
134
partBody = part ;
112
135
}
113
136
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" );
116
142
}
117
143
118
144
DefaultPartBuilder builder = new DefaultPartBuilder (partHeaders , partBody );
145
+ if (contentType != null ) {
146
+ builder .header (HttpHeaders .CONTENT_TYPE , contentType .toString ());
147
+ }
119
148
this .parts .add (name , builder );
120
149
return builder ;
121
150
}
122
151
123
152
/**
124
- * Add an asynchronous part with {@link Publisher}-based content.
153
+ * Add a part from {@link Publisher} content.
125
154
* @param name the name of the part to add
126
155
* @param publisher the part contents
127
156
* @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
132
161
Assert .notNull (publisher , "'publisher' must not be null" );
133
162
Assert .notNull (elementClass , "'elementClass' must not be null" );
134
163
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 );
137
165
this .parts .add (name , builder );
138
166
return builder ;
139
167
140
168
}
141
169
142
170
/**
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.
146
173
* @param name the name of the part to add
147
174
* @param publisher the part contents
148
175
* @param typeReference the type of elements contained in the publisher
@@ -155,8 +182,7 @@ public <T, P extends Publisher<T>> PartBuilder asyncPart(
155
182
Assert .notNull (publisher , "'publisher' must not be null" );
156
183
Assert .notNull (typeReference , "'typeReference' must not be null" );
157
184
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 );
160
186
this .parts .add (name , builder );
161
187
return builder ;
162
188
}
@@ -201,28 +227,36 @@ public interface PartBuilder {
201
227
202
228
private static class DefaultPartBuilder implements PartBuilder {
203
229
204
- protected final HttpHeaders headers ;
230
+ @ Nullable
231
+ protected HttpHeaders headers ;
205
232
206
233
@ Nullable
207
234
protected final Object body ;
208
235
209
- public DefaultPartBuilder (HttpHeaders headers , @ Nullable Object body ) {
236
+ public DefaultPartBuilder (@ Nullable HttpHeaders headers , @ Nullable Object body ) {
210
237
this .headers = headers ;
211
238
this .body = body ;
212
239
}
213
240
214
241
@ Override
215
242
public PartBuilder header (String headerName , String ... headerValues ) {
216
- this . headers .addAll (headerName , Arrays .asList (headerValues ));
243
+ initHeadersIfNecessary () .addAll (headerName , Arrays .asList (headerValues ));
217
244
return this ;
218
245
}
219
246
220
247
@ Override
221
248
public PartBuilder headers (Consumer <HttpHeaders > headersConsumer ) {
222
- headersConsumer .accept (this . headers );
249
+ headersConsumer .accept (initHeadersIfNecessary () );
223
250
return this ;
224
251
}
225
252
253
+ private HttpHeaders initHeadersIfNecessary () {
254
+ if (this .headers == null ) {
255
+ this .headers = new HttpHeaders ();
256
+ }
257
+ return this .headers ;
258
+ }
259
+
226
260
public HttpEntity <?> build () {
227
261
return new HttpEntity <>(this .body , this .headers );
228
262
}
@@ -233,14 +267,14 @@ private static class PublisherPartBuilder<S, P extends Publisher<S>> extends Def
233
267
234
268
private final ResolvableType resolvableType ;
235
269
236
- public PublisherPartBuilder (HttpHeaders headers , P body , Class <S > elementClass ) {
270
+ public PublisherPartBuilder (@ Nullable HttpHeaders headers , P body , Class <S > elementClass ) {
237
271
super (headers , body );
238
272
this .resolvableType = ResolvableType .forClass (elementClass );
239
273
}
240
274
241
- public PublisherPartBuilder (HttpHeaders headers , P body , ParameterizedTypeReference <S > typeReference ) {
275
+ public PublisherPartBuilder (@ Nullable HttpHeaders headers , P body , ParameterizedTypeReference <S > typeRef ) {
242
276
super (headers , body );
243
- this .resolvableType = ResolvableType .forType (typeReference );
277
+ this .resolvableType = ResolvableType .forType (typeRef );
244
278
}
245
279
246
280
public PublisherPartBuilder (PublisherEntity <S , P > other ) {
@@ -265,7 +299,7 @@ public HttpEntity<?> build() {
265
299
* @param <T> the type contained in the publisher
266
300
* @param <P> the publisher
267
301
*/
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 >
269
303
implements ResolvableTypeProvider {
270
304
271
305
private final ResolvableType resolvableType ;
0 commit comments