/**
* 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.camel.dataformat.mime.multipart;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.ParseException;
import javax.mail.util.ByteArrayDataSource;
import org.apache.camel.Attachment;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.impl.DefaultAttachment;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.MessageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MimeMultipartDataFormat implements DataFormat {
private static final Logger LOG = LoggerFactory.getLogger(MimeMultipartDataFormat.class);
private static final String MIME_VERSION = "MIME-Version";
private static final String CONTENT_TYPE = "Content-Type";
private static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
private static final String[] STANDARD_HEADERS = {"Message-ID", "MIME-Version", "Content-Type"};
private String multipartSubType = "mixed";
private boolean multipartWithoutAttachment;
private boolean headersInline;
private Pattern includeHeaders;
private boolean binaryContent;
public void setBinaryContent(boolean binaryContent) {
this.binaryContent = binaryContent;
}
public void setHeadersInline(boolean headersInline) {
this.headersInline = headersInline;
}
public void setIncludeHeaders(String includeHeaders) {
this.includeHeaders = Pattern.compile(includeHeaders, Pattern.CASE_INSENSITIVE);
}
public void setMultipartWithoutAttachment(boolean multipartWithoutAttachment) {
this.multipartWithoutAttachment = multipartWithoutAttachment;
}
public void setMultipartSubType(String multipartSubType) {
this.multipartSubType = multipartSubType;
}
@Override
public void marshal(Exchange exchange, Object graph, OutputStream stream)
throws NoTypeConversionAvailableException, MessagingException, IOException {
if (multipartWithoutAttachment || headersInline || exchange.getIn().hasAttachments()) {
ContentType contentType = getContentType(exchange);
// remove the Content-Type header. This will be wrong afterwards...
exchange.getOut().removeHeader(Exchange.CONTENT_TYPE);
byte[] bodyContent = ExchangeHelper.convertToMandatoryType(exchange, byte[].class, graph);
Session session = Session.getInstance(System.getProperties());
MimeMessage mm = new MimeMessage(session);
MimeMultipart mp = new MimeMultipart(multipartSubType);
BodyPart part = new MimeBodyPart();
writeBodyPart(bodyContent, part, contentType);
mp.addBodyPart(part);
for (Map.Entry<String, Attachment> entry : exchange.getIn().getAttachmentObjects().entrySet()) {
String attachmentFilename = entry.getKey();
Attachment attachment = entry.getValue();
part = new MimeBodyPart();
part.setDataHandler(attachment.getDataHandler());
part.setFileName(MimeUtility.encodeText(attachmentFilename, "UTF-8", null));
String ct = attachment.getDataHandler().getContentType();
contentType = new ContentType(ct);
part.setHeader(CONTENT_TYPE, ct);
if (!contentType.match("text/*") && binaryContent) {
part.setHeader(CONTENT_TRANSFER_ENCODING, "binary");
}
// Set headers to the attachment
for (String headerName : attachment.getHeaderNames()) {
List<String> values = attachment.getHeaderAsList(headerName);
for (String value : values) {
part.setHeader(headerName, value);
}
}
mp.addBodyPart(part);
exchange.getOut().removeAttachment(attachmentFilename);
}
mm.setContent(mp);
// copy headers if required and if the content can be converted into
// a String
if (headersInline && includeHeaders != null) {
for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
if (includeHeaders.matcher(entry.getKey()).matches()) {
String headerStr = ExchangeHelper.convertToType(exchange, String.class, entry.getValue());
if (headerStr != null) {
mm.setHeader(entry.getKey(), headerStr);
}
}
}
}
mm.saveChanges();
Enumeration<?> hl = mm.getAllHeaders();
List<String> headers = new ArrayList<String>();
if (!headersInline) {
while (hl.hasMoreElements()) {
Object ho = hl.nextElement();
if (ho instanceof Header) {
Header h = (Header) ho;
exchange.getOut().setHeader(h.getName(), h.getValue());
headers.add(h.getName());
}
}
mm.saveChanges();
}
mm.writeTo(stream, headers.toArray(new String[0]));
} else {
// keep the original data
InputStream is = ExchangeHelper.convertToMandatoryType(exchange, InputStream.class, graph);
IOHelper.copyAndCloseInput(is, stream);
}
}
private ContentType getContentType(Exchange exchange) throws ParseException {
String contentTypeStr = ExchangeHelper.getContentType(exchange);
if (contentTypeStr == null) {
contentTypeStr = DEFAULT_CONTENT_TYPE;
}
ContentType contentType = new ContentType(contentTypeStr);
String contentEncoding = ExchangeHelper.getContentEncoding(exchange);
// add a charset parameter for text subtypes
if (contentEncoding != null && contentType.match("text/*")) {
contentType.setParameter("charset", MimeUtility.mimeCharset(contentEncoding));
}
return contentType;
}
private void writeBodyPart(byte[] bodyContent, Part part, ContentType contentType) throws MessagingException {
DataSource ds = new ByteArrayDataSource(bodyContent, contentType.toString());
part.setDataHandler(new DataHandler(ds));
part.setHeader(CONTENT_TYPE, contentType.toString());
if (contentType.match("text/*")) {
part.setHeader(CONTENT_TRANSFER_ENCODING, "8bit");
} else if (binaryContent) {
part.setHeader(CONTENT_TRANSFER_ENCODING, "binary");
} else {
part.setHeader(CONTENT_TRANSFER_ENCODING, "base64");
}
}
@Override
public Object unmarshal(Exchange exchange, InputStream stream) throws IOException, MessagingException {
MimeBodyPart mimeMessage;
String contentType;
Message camelMessage;
Object content = null;
if (headersInline) {
mimeMessage = new MimeBodyPart(stream);
camelMessage = exchange.getOut();
MessageHelper.copyHeaders(exchange.getIn(), camelMessage, true);
contentType = mimeMessage.getHeader(CONTENT_TYPE, null);
// write the MIME headers not generated by javamail as Camel headers
Enumeration<?> headersEnum = mimeMessage.getNonMatchingHeaders(STANDARD_HEADERS);
while (headersEnum.hasMoreElements()) {
Object ho = headersEnum.nextElement();
if (ho instanceof Header) {
Header header = (Header) ho;
camelMessage.setHeader(header.getName(), header.getValue());
}
}
} else {
// check if this a multipart at all. Otherwise do nothing
contentType = exchange.getIn().getHeader(CONTENT_TYPE, String.class);
if (contentType == null) {
return stream;
}
try {
ContentType ct = new ContentType(contentType);
if (!ct.match("multipart/*")) {
return stream;
}
} catch (ParseException e) {
LOG.warn("Invalid Content-Type " + contentType + " ignored");
return stream;
}
camelMessage = exchange.getOut();
MessageHelper.copyHeaders(exchange.getIn(), camelMessage, true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOHelper.copyAndCloseInput(stream, bos);
InternetHeaders headers = new InternetHeaders();
extractHeader(CONTENT_TYPE, camelMessage, headers);
extractHeader(MIME_VERSION, camelMessage, headers);
mimeMessage = new MimeBodyPart(headers, bos.toByteArray());
bos.close();
}
DataHandler dh;
try {
dh = mimeMessage.getDataHandler();
if (dh != null) {
content = dh.getContent();
contentType = dh.getContentType();
}
} catch (MessagingException e) {
LOG.warn("cannot parse message, no unmarshalling done");
}
if (content instanceof MimeMultipart) {
MimeMultipart mp = (MimeMultipart) content;
content = mp.getBodyPart(0);
for (int i = 1; i < mp.getCount(); i++) {
BodyPart bp = mp.getBodyPart(i);
DefaultAttachment camelAttachment = new DefaultAttachment(bp.getDataHandler());
@SuppressWarnings("unchecked")
Enumeration<Header> headers = bp.getAllHeaders();
while (headers.hasMoreElements()) {
Header header = headers.nextElement();
camelAttachment.addHeader(header.getName(), header.getValue());
}
camelMessage.addAttachmentObject(getAttachmentKey(bp), camelAttachment);
}
}
if (content instanceof BodyPart) {
BodyPart bp = (BodyPart) content;
camelMessage.setBody(bp.getInputStream());
contentType = bp.getContentType();
if (contentType != null && !DEFAULT_CONTENT_TYPE.equals(contentType)) {
camelMessage.setHeader(CONTENT_TYPE, contentType);
ContentType ct = new ContentType(contentType);
String charset = ct.getParameter("charset");
if (charset != null) {
camelMessage.setHeader(Exchange.CONTENT_ENCODING, MimeUtility.javaCharset(charset));
}
}
} else {
// If we find no body part, try to leave the message alone
LOG.info("no MIME part found");
}
return camelMessage;
}
private void extractHeader(String headerMame, Message camelMessage, InternetHeaders headers) {
String h = camelMessage.getHeader(headerMame, String.class);
if (h != null) {
headers.addHeader(headerMame, h);
camelMessage.removeHeader(headerMame);
}
}
private String getAttachmentKey(BodyPart bp) throws MessagingException, UnsupportedEncodingException {
// use the filename as key for the map
String key = bp.getFileName();
// if there is no file name we use the Content-ID header
if (key == null && bp instanceof MimeBodyPart) {
key = ((MimeBodyPart) bp).getContentID();
if (key != null && key.startsWith("<") && key.length() > 2) {
// strip <>
key = key.substring(1, key.length() - 1);
}
}
// or a generated content id
if (key == null) {
key = UUID.randomUUID().toString() + "@camel.apache.org";
}
return MimeUtility.decodeText(key);
}
}