com.rt.storage.api.client.http.MultipartContent Maven / Gradle / Ivy
package com.rt.storage.api.client.http;
import com.rt.storage.api.client.util.Preconditions;
import com.rt.storage.api.client.util.StreamingContent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
/**
* Serializes MIME multipart content as specified by RFC 2387: The MIME Multipart/Related Content-type
* and RFC 2046: Multipurpose Internet
* Mail Extensions: The Multipart/mixed (primary) subtype.
*
* By default the media type is {@code "multipart/related; boundary=__END_OF_PART____"}, but this may be customized by calling {@link #setMediaType(HttpMediaType)}, {@link
* #getMediaType()}, or {@link #setBoundary(String)}.
*
* Implementation is not thread-safe.
*
* @since 1.14
* @author Yaniv Inbar
*/
public class MultipartContent extends AbstractHttpContent {
static final String NEWLINE = "\r\n";
private static final String TWO_DASHES = "--";
/** Parts of the HTTP multipart request. */
private ArrayList parts = new ArrayList<>();
public MultipartContent() {
this("__END_OF_PART__" + UUID.randomUUID().toString() + "__");
}
public MultipartContent(String boundary) {
super(new HttpMediaType("multipart/related").setParameter("boundary", boundary));
}
public void writeTo(OutputStream out) throws IOException {
Writer writer = new OutputStreamWriter(out, getCharset());
String boundary = getBoundary();
for (Part part : parts) {
HttpHeaders headers = new HttpHeaders().setAcceptEncoding(null);
if (part.headers != null) {
headers.fromHttpHeaders(part.headers);
}
headers
.setContentEncoding(null)
.setUserAgent(null)
.setContentType(null)
.setContentLength(null)
.set("Content-Transfer-Encoding", null);
// analyze the content
HttpContent content = part.content;
StreamingContent streamingContent = null;
if (content != null) {
headers.set("Content-Transfer-Encoding", Arrays.asList("binary"));
headers.setContentType(content.getType());
HttpEncoding encoding = part.encoding;
long contentLength;
if (encoding == null) {
contentLength = content.getLength();
streamingContent = content;
} else {
headers.setContentEncoding(encoding.getName());
streamingContent = new HttpEncodingStreamingContent(content, encoding);
contentLength = AbstractHttpContent.computeLength(content);
}
if (contentLength != -1) {
headers.setContentLength(contentLength);
}
}
// write multipart-body from RFC 1521 §7.2.1
// write encapsulation
// write delimiter
writer.write(TWO_DASHES);
writer.write(boundary);
writer.write(NEWLINE);
// write body-part; message from RFC 822 §4.1
// write message fields
HttpHeaders.serializeHeadersForMultipartRequests(headers, null, null, writer);
if (streamingContent != null) {
writer.write(NEWLINE);
writer.flush();
// write message text/body
streamingContent.writeTo(out);
}
// terminate encapsulation
writer.write(NEWLINE);
}
// write close-delimiter
writer.write(TWO_DASHES);
writer.write(boundary);
writer.write(TWO_DASHES);
writer.write(NEWLINE);
writer.flush();
}
@Override
public boolean retrySupported() {
for (Part part : parts) {
if (!part.content.retrySupported()) {
return false;
}
}
return true;
}
@Override
public MultipartContent setMediaType(HttpMediaType mediaType) {
super.setMediaType(mediaType);
return this;
}
/** Returns an unmodifiable view of the parts of the HTTP multipart request. */
public final Collection getParts() {
return Collections.unmodifiableCollection(parts);
}
/**
* Adds an HTTP multipart part.
*
* Overriding is only supported for the purpose of calling the super implementation and
* changing the return type, but nothing else.
*/
public MultipartContent addPart(Part part) {
parts.add(Preconditions.checkNotNull(part));
return this;
}
/**
* Sets the parts of the HTTP multipart request.
*
*
Overriding is only supported for the purpose of calling the super implementation and
* changing the return type, but nothing else.
*/
public MultipartContent setParts(Collection parts) {
this.parts = new ArrayList<>(parts);
return this;
}
/**
* Sets the HTTP content parts of the HTTP multipart request, where each part is assumed to have
* no HTTP headers and no encoding.
*
* Overriding is only supported for the purpose of calling the super implementation and
* changing the return type, but nothing else.
*/
public MultipartContent setContentParts(Collection extends HttpContent> contentParts) {
this.parts = new ArrayList<>(contentParts.size());
for (HttpContent contentPart : contentParts) {
addPart(new Part(contentPart));
}
return this;
}
/** Returns the boundary string to use. */
public final String getBoundary() {
return getMediaType().getParameter("boundary");
}
/**
* Sets the boundary string to use.
*
*
Defaults to {@code "END_OF_PART"}.
*
*
Overriding is only supported for the purpose of calling the super implementation and
* changing the return type, but nothing else.
*/
public MultipartContent setBoundary(String boundary) {
getMediaType().setParameter("boundary", Preconditions.checkNotNull(boundary));
return this;
}
/**
* Single part of a multi-part request.
*
*
Implementation is not thread-safe.
*/
public static final class Part {
/** HTTP content or {@code null} for none. */
HttpContent content;
/** HTTP headers or {@code null} for none. */
HttpHeaders headers;
/** HTTP encoding or {@code null} for none. */
HttpEncoding encoding;
public Part() {
this(null);
}
/** @param content HTTP content or {@code null} for none */
public Part(HttpContent content) {
this(null, content);
}
/**
* @param headers HTTP headers or {@code null} for none
* @param content HTTP content or {@code null} for none
*/
public Part(HttpHeaders headers, HttpContent content) {
setHeaders(headers);
setContent(content);
}
/** Sets the HTTP content or {@code null} for none. */
public Part setContent(HttpContent content) {
this.content = content;
return this;
}
/** Returns the HTTP content or {@code null} for none. */
public HttpContent getContent() {
return content;
}
/** Sets the HTTP headers or {@code null} for none. */
public Part setHeaders(HttpHeaders headers) {
this.headers = headers;
return this;
}
/** Returns the HTTP headers or {@code null} for none. */
public HttpHeaders getHeaders() {
return headers;
}
/** Sets the HTTP encoding or {@code null} for none. */
public Part setEncoding(HttpEncoding encoding) {
this.encoding = encoding;
return this;
}
/** Returns the HTTP encoding or {@code null} for none. */
public HttpEncoding getEncoding() {
return encoding;
}
}
}