blob: c6d2dc73545f35c50a479b3afd2fb70e57d0d0b0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.axiom.mime;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import org.apache.axiom.blob.Blob;
import org.apache.axiom.util.UIDGenerator;
/**
* Writes a MIME multipart body as used by XOP/MTOM and SOAP with Attachments. MIME parts are
* written using {@link #writePart(ContentType, ContentTransferEncoding, String, List)} or
* {@link #writePart(Blob, ContentType, ContentTransferEncoding, String, List)}. Calls to both methods can be mixed, i.e.
* it is not required to use the same method for all MIME parts. Instead, the caller should choose
* the most convenient method for each part (depending on the form in which the content is
* available). After all parts have been written, {@link #complete()} must be called to write the
* final MIME boundary.
* <p>
* The following semantics are defined for the {@code contentTransferEncoding} and {@code contentID}
* arguments of the two write methods:
* <ul>
* <li>The content transfer encoding specified by the {@code contentTransferEncoding} argument is
* applied by the write method; the caller only provides the unencoded data. The implementation
* ensures that the MIME part has a
* {@code Content-Transfer-Encoding} header appropriate for the applied encoding.</li>
* <li>The content ID passed as argument is always the raw ID (without the angle brackets). The
* implementation translates this into a properly formatted {@code Content-ID} header.</li>
* </ul>
*/
public final class MultipartBodyWriter {
class PartOutputStream extends OutputStream {
private final OutputStream parent;
public PartOutputStream(OutputStream parent) {
this.parent = parent;
}
@Override
public void write(int b) throws IOException {
parent.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
parent.write(b, off, len);
}
@Override
public void write(byte[] b) throws IOException {
parent.write(b);
}
@Override
public void close() throws IOException {
writeAscii("\r\n");
}
}
private final OutputStream out;
private final String boundary;
private final byte[] buffer = new byte[256];
/**
* Constructor.
*
* @param out
* the output stream to write the multipart body to
* @param boundary
* the MIME boundary
*
* @see UIDGenerator#generateMimeBoundary()
*/
public MultipartBodyWriter(OutputStream out, String boundary) {
this.out = out;
this.boundary = boundary;
}
void writeAscii(String s) throws IOException {
int count = 0;
for (int i=0, len=s.length(); i<len; i++) {
char c = s.charAt(i);
if (c >= 128) {
throw new IOException("Illegal character '" + c + "'");
}
buffer[count++] = (byte)c;
if (count == buffer.length) {
out.write(buffer);
count = 0;
}
}
if (count > 0) {
out.write(buffer, 0, count);
}
}
/**
* Start writing a MIME part. The methods returns an {@link OutputStream} that the caller can
* use to write the content of the MIME part. After writing the content,
* {@link OutputStream#close()} must be called to complete the writing of the MIME part.
*
* @param contentType
* the content type of the MIME part; may be {@code null}
* @param contentTransferEncoding
* the content transfer encoding to be used (see above); must not be
* <code>null</code>
* @param contentID
* the content ID of the MIME part (see above); may be {@code null}
* @param extraHeaders
* a list of {@link Header} objects with additional headers to write to the MIME
* part; may be {@code null}
* @return an output stream to write the content of the MIME part
* @throws IOException
* if an I/O error occurs when writing to the underlying stream
*/
public OutputStream writePart(ContentType contentType, ContentTransferEncoding contentTransferEncoding,
String contentID, List<Header> extraHeaders) throws IOException {
writeAscii("--");
writeAscii(boundary);
// RFC 2046 explicitly says that Content-Type is not mandatory (and defaults to
// text/plain; charset=us-ascii).
if (contentType != null) {
writeAscii("\r\nContent-Type: ");
writeAscii(contentType.toString());
}
writeAscii("\r\nContent-Transfer-Encoding: ");
writeAscii(contentTransferEncoding.toString());
if (contentID != null) {
writeAscii("\r\nContent-ID: <");
writeAscii(contentID);
out.write('>');
}
if (extraHeaders != null) {
for (Header header : extraHeaders) {
writeAscii("\r\n");
writeAscii(header.getName());
writeAscii(": ");
writeAscii(header.getValue());
}
}
writeAscii("\r\n\r\n");
return contentTransferEncoding.encode(new PartOutputStream(out));
}
/**
* Write a MIME part.
*
* @param blob
* the content of the MIME part to write
* @param contentType
* the content type; may be {@code null}
* @param contentTransferEncoding
* the content transfer encoding to be used (see above); must not be
* <code>null</code>
* @param contentID
* the content ID of the MIME part (see above)
* @param extraHeaders
* a list of {@link Header} objects with additional headers to write to the MIME part
* @throws IOException
* if an I/O error occurs when writing the part to the underlying stream
*/
public void writePart(Blob blob, ContentType contentType, ContentTransferEncoding contentTransferEncoding, String contentID, List<Header> extraHeaders)
throws IOException {
OutputStream partOutputStream = writePart(contentType, contentTransferEncoding, contentID, extraHeaders);
blob.writeTo(partOutputStream);
partOutputStream.close();
}
/**
* Complete writing of the MIME multipart package. This method does <b>not</b> close the
* underlying stream.
*
* @throws IOException
* if an I/O error occurs when writing to the underlying stream
*/
public void complete() throws IOException {
writeAscii("--");
writeAscii(boundary);
writeAscii("--\r\n");
}
}