From 49e5c4dcf6ea4959cefda7b531d99fc880654911 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 4 Jun 2019 16:40:59 -0400 Subject: [PATCH] Polish MultipartBodyBuilder Improve Javadoc Consistently reject Publisher unless using asyncPart Consistently set Content-Type when specified --- .../http/client/MultipartBodyBuilder.java | 108 ++++++++++++------ 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java b/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java index 1e42dc2459..a3f40f4349 100644 --- a/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java @@ -36,19 +36,39 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * A mutable builder for multipart form bodies. For example: + * Builder for the body of a multipart request, producing + * {@code MultiValueMap}, which can be provided to the + * {@code WebClient} through the {@code syncBody} method. + * + * Examples: *
  *
+ * // Add form field
  * MultipartBodyBuilder builder = new MultipartBodyBuilder();
- * builder.part("form field", "form value");
+ * builder.part("form field", "form value").header("foo", "bar");
  *
+ * // Add file part
  * Resource image = new ClassPathResource("image.jpg");
- * builder.part("image", image).header("Baz", "Qux");
+ * builder.part("image", image).header("foo", "bar");
+ *
+ * // Add content (e.g. JSON)
+ * Account account = ...
+ * builder.part("account", account).header("foo", "bar");
+ *
+ * // Add content from Publisher
+ * Mono<Account> accountMono = ...
+ * builder.asyncPart("account", accountMono).header("foo", "bar");
  *
+ * // Build and use
  * MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
- * // use multipartBody with RestTemplate or WebClient
+ *
+ * Mono<Void> result = webClient.post()
+ *     .uri("...")
+ *     .syncBody(multipartBody)
+ *     .retrieve()
+ *     .bodyToMono(Void.class)
  * 
- + * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 5.0.2 @@ -67,7 +87,14 @@ public final class MultipartBodyBuilder { /** - * Add a part from an Object. + * Add a part where the Object may be: + * * @param name the name of the part to add * @param part the part data * @return builder that allows for further customization of part headers @@ -77,51 +104,53 @@ public final class MultipartBodyBuilder { } /** - * Variant of {@link #part(String, Object)} that also accepts a MediaType - * which is used to determine how to encode the part. + * Variant of {@link #part(String, Object)} that also accepts a MediaType. * @param name the name of the part to add * @param part the part data - * @param contentType the media type for the part + * @param contentType the media type to help with encoding the part * @return builder that allows for further customization of part headers */ public PartBuilder part(String name, Object part, @Nullable MediaType contentType) { Assert.hasLength(name, "'name' must not be empty"); Assert.notNull(part, "'part' must not be null"); - if (part instanceof Publisher) { - throw new IllegalArgumentException("Use publisher(String, Publisher, Class) or " + - "publisher(String, Publisher, ParameterizedTypeReference) for adding Publisher parts"); - } - if (part instanceof PublisherEntity) { PublisherPartBuilder builder = new PublisherPartBuilder<>((PublisherEntity) part); + if (contentType != null) { + builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString()); + } this.parts.add(name, builder); return builder; } Object partBody; - HttpHeaders partHeaders = new HttpHeaders(); - + HttpHeaders partHeaders = null; if (part instanceof HttpEntity) { - HttpEntity httpEntity = (HttpEntity) part; - partBody = httpEntity.getBody(); - partHeaders.addAll(httpEntity.getHeaders()); + partBody = ((HttpEntity) part).getBody(); + partHeaders = new HttpHeaders(); + partHeaders.putAll(((HttpEntity) part).getHeaders()); } else { partBody = part; } - if (contentType != null) { - partHeaders.setContentType(contentType); + if (partBody instanceof Publisher) { + throw new IllegalArgumentException( + "Use asyncPart(String, Publisher, Class)" + + " or asyncPart(String, Publisher, ParameterizedTypeReference) or" + + " or MultipartBodyBuilder.PublisherEntity"); } DefaultPartBuilder builder = new DefaultPartBuilder(partHeaders, partBody); + if (contentType != null) { + builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString()); + } this.parts.add(name, builder); return builder; } /** - * Add an asynchronous part with {@link Publisher}-based content. + * Add a part from {@link Publisher} content. * @param name the name of the part to add * @param publisher the part contents * @param elementClass the type of elements contained in the publisher @@ -132,17 +161,15 @@ public final class MultipartBodyBuilder { Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(elementClass, "'elementClass' must not be null"); - HttpHeaders headers = new HttpHeaders(); - PublisherPartBuilder builder = new PublisherPartBuilder<>(headers, publisher, elementClass); + PublisherPartBuilder builder = new PublisherPartBuilder<>(null, publisher, elementClass); this.parts.add(name, builder); return builder; } /** - * Variant of {@link #asyncPart(String, Publisher, Class)} that accepts a - * {@link ParameterizedTypeReference} for the element type, which allows - * specifying generic type information. + * Variant of {@link #asyncPart(String, Publisher, Class)} with a + * {@link ParameterizedTypeReference} for the element type information. * @param name the name of the part to add * @param publisher the part contents * @param typeReference the type of elements contained in the publisher @@ -155,8 +182,7 @@ public final class MultipartBodyBuilder { Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(typeReference, "'typeReference' must not be null"); - HttpHeaders headers = new HttpHeaders(); - PublisherPartBuilder builder = new PublisherPartBuilder<>(headers, publisher, typeReference); + PublisherPartBuilder builder = new PublisherPartBuilder<>(null, publisher, typeReference); this.parts.add(name, builder); return builder; } @@ -201,28 +227,36 @@ public final class MultipartBodyBuilder { private static class DefaultPartBuilder implements PartBuilder { - protected final HttpHeaders headers; + @Nullable + protected HttpHeaders headers; @Nullable protected final Object body; - public DefaultPartBuilder(HttpHeaders headers, @Nullable Object body) { + public DefaultPartBuilder(@Nullable HttpHeaders headers, @Nullable Object body) { this.headers = headers; this.body = body; } @Override public PartBuilder header(String headerName, String... headerValues) { - this.headers.addAll(headerName, Arrays.asList(headerValues)); + initHeadersIfNecessary().addAll(headerName, Arrays.asList(headerValues)); return this; } @Override public PartBuilder headers(Consumer headersConsumer) { - headersConsumer.accept(this.headers); + headersConsumer.accept(initHeadersIfNecessary()); return this; } + private HttpHeaders initHeadersIfNecessary() { + if (this.headers == null) { + this.headers = new HttpHeaders(); + } + return this.headers; + } + public HttpEntity build() { return new HttpEntity<>(this.body, this.headers); } @@ -233,14 +267,14 @@ public final class MultipartBodyBuilder { private final ResolvableType resolvableType; - public PublisherPartBuilder(HttpHeaders headers, P body, Class elementClass) { + public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, Class elementClass) { super(headers, body); this.resolvableType = ResolvableType.forClass(elementClass); } - public PublisherPartBuilder(HttpHeaders headers, P body, ParameterizedTypeReference typeReference) { + public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, ParameterizedTypeReference typeRef) { super(headers, body); - this.resolvableType = ResolvableType.forType(typeReference); + this.resolvableType = ResolvableType.forType(typeRef); } public PublisherPartBuilder(PublisherEntity other) { @@ -265,7 +299,7 @@ public final class MultipartBodyBuilder { * @param the type contained in the publisher * @param

the publisher */ - public static final class PublisherEntity> extends HttpEntity

+ static final class PublisherEntity> extends HttpEntity

implements ResolvableTypeProvider { private final ResolvableType resolvableType;