Skip to content
Permalink
Browse files

Add MultipartBodyBuilder

This commit introduces the MultipartBodyBuilder, a builder for multipart
form bodies.

Issue: SPR-16134
  • Loading branch information...
poutsma committed Nov 2, 2017
1 parent 9b7af8b commit 2d1f87501c043fc58200ce4d1b0f544f8ad04619
@@ -0,0 +1,162 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.http.client;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
* A mutable builder for multipart form bodies. For example:
* <pre class="code">
*
* MultipartBodyBuilder builder = new MultipartBodyBuilder();
* MultiValueMap&lt;String, String&gt; form = new LinkedMultiValueMap&lt;&gt;();
* form.add("form field", "form value");
* builder.part("form", form).header("Foo", "Bar");
*
* Resource image = new ClassPathResource("image.jpg");
* builder.part("image", image).header("Baz", "Qux");
*
* MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
* // use multipartBody with RestTemplate or WebClient
* </pre>
* @author Arjen Poutsma
* @since 5.0.2
* @see <a href="https://tools.ietf.org/html/rfc7578">RFC 7578</a>
*/
public final class MultipartBodyBuilder {

private final LinkedMultiValueMap<String, DefaultPartBuilder> parts = new LinkedMultiValueMap<>();


/**
* Creates a new, empty instance of the {@code MultipartBodyBuilder}.
*/
public MultipartBodyBuilder() {
}


/**
* Builds the multipart body.
* @return the built body
*/
public MultiValueMap<String, HttpEntity<?>> build() {
MultiValueMap<String, HttpEntity<?>> result = new LinkedMultiValueMap<>(this.parts.size());
for (Map.Entry<String, List<DefaultPartBuilder>> entry : this.parts.entrySet()) {
for (DefaultPartBuilder builder : entry.getValue()) {
HttpEntity<?> entity = builder.build();
result.add(entry.getKey(), entity);
}
}
return result;
}

/**
* Adds a part to this builder, allowing for further header customization with the returned
* {@link PartBuilder}.
* @param name the name of the part to add (may not be empty)
* @param part the part to add
* @return a builder that allows for further header customization
*/
public PartBuilder part(String name, Object part) {
return part(name, part, null);
}

/**
* Adds a part to this builder, allowing for further header customization with the returned
* {@link PartBuilder}.
* @param name the name of the part to add (may not be empty)
* @param part the part to add
* @param contentType the {@code Content-Type} header for the part (may be {@code null})
* @return a builder that allows for further header customization
*/
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");

Object partBody;
HttpHeaders partHeaders = new HttpHeaders();

if (part instanceof HttpEntity) {
HttpEntity<?> other = (HttpEntity<?>) part;
partBody = other.getBody();
partHeaders.addAll(other.getHeaders());
}
else {
partBody = part;
}

if (contentType != null) {
partHeaders.setContentType(contentType);
}
DefaultPartBuilder builder = new DefaultPartBuilder(partBody, partHeaders);
this.parts.add(name, builder);
return builder;
}


/**
* Builder interface that allows for customization of part headers.
*/
public interface PartBuilder {

/**
* Add the given part-specific header values under the given name.
* @param headerName the part header name
* @param headerValues the part header value(s)
* @return this builder
* @see HttpHeaders#add(String, String)
*/
PartBuilder header(String headerName, String... headerValues);
}


private static class DefaultPartBuilder implements PartBuilder {

@Nullable
private final Object body;

private final HttpHeaders headers;


public DefaultPartBuilder(@Nullable Object body, HttpHeaders headers) {
this.body = body;
this.headers = headers;
}

@Override
public PartBuilder header(String headerName, String... headerValues) {
this.headers.addAll(headerName, Arrays.asList(headerValues));
return this;
}

public HttpEntity<?> build() {
return new HttpEntity<>(this.body, this.headers);
}
}

}
@@ -0,0 +1,67 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.http.client;

import org.junit.Test;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import static org.junit.Assert.*;

/**
* @author Arjen Poutsma
*/
public class MultipartBodyBuilderTests {

@Test
public void builder() throws Exception {
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("form field", "form value");
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.add("foo", "bar");
HttpEntity<String> entity = new HttpEntity<>("body", entityHeaders);

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("key", form).header("foo", "bar");
builder.part("logo", logo).header("baz", "qux");
builder.part("entity", entity).header("baz", "qux");

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

assertEquals(3, result.size());
assertNotNull(result.getFirst("key"));
assertEquals(form, result.getFirst("key").getBody());
assertEquals("bar", result.getFirst("key").getHeaders().getFirst("foo"));

assertNotNull(result.getFirst("logo"));
assertEquals(logo, result.getFirst("logo").getBody());
assertEquals("qux", result.getFirst("logo").getHeaders().getFirst("baz"));

assertNotNull(result.getFirst("entity"));
assertEquals("body", result.getFirst("entity").getBody());
assertEquals("bar", result.getFirst("entity").getHeaders().getFirst("foo"));
assertEquals("qux", result.getFirst("entity").getHeaders().getFirst("baz"));
}


}

0 comments on commit 2d1f875

Please sign in to comment.
You can’t perform that action at this time.