CAMEL-9283: Data Format for MIME-Multipart. Thanks to Stephan Siano for the patch.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/08aa98f8 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/08aa98f8 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/08aa98f8 Branch: refs/heads/master Commit: 08aa98f8bc771b7edf2a51978c94835d0cd3624c Parents: 74eda66 Author: Claus Ibsen <davscl...@apache.org> Authored: Fri Nov 13 16:39:58 2015 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Fri Nov 13 17:35:02 2015 +0100 ---------------------------------------------------------------------- .../apache/camel/builder/DataFormatClause.java | 63 ++++- .../model/dataformat/DataFormatsDefinition.java | 1 + .../dataformat/MimeMultipartDataFormat.java | 124 +++++++++ .../apache/camel/model/dataformat/jaxb.index | 1 + components/camel-mail/pom.xml | 5 +- .../mime/multipart/MimeMultipartDataFormat.java | 241 ++++++++++++++++ .../org/apache/camel/dataformat/mime-multipart | 18 ++ .../multipart/MimeMultipartDataFormatTest.java | 275 +++++++++++++++++++ 8 files changed, 724 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/camel-core/src/main/java/org/apache/camel/builder/DataFormatClause.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/DataFormatClause.java b/camel-core/src/main/java/org/apache/camel/builder/DataFormatClause.java index 9495f4c..689ef1f 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/DataFormatClause.java +++ b/camel-core/src/main/java/org/apache/camel/builder/DataFormatClause.java @@ -20,8 +20,6 @@ import java.nio.charset.Charset; import java.util.Map; import java.util.zip.Deflater; -import org.w3c.dom.Node; - import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.dataformat.AvroDataFormat; @@ -41,6 +39,7 @@ import org.apache.camel.model.dataformat.JaxbDataFormat; import org.apache.camel.model.dataformat.JibxDataFormat; import org.apache.camel.model.dataformat.JsonDataFormat; import org.apache.camel.model.dataformat.JsonLibrary; +import org.apache.camel.model.dataformat.MimeMultipartDataFormat; import org.apache.camel.model.dataformat.PGPDataFormat; import org.apache.camel.model.dataformat.ProtobufDataFormat; import org.apache.camel.model.dataformat.RssDataFormat; @@ -57,6 +56,7 @@ import org.apache.camel.model.dataformat.ZipDataFormat; import org.apache.camel.model.dataformat.ZipFileDataFormat; import org.apache.camel.util.CollectionStringBuffer; import org.apache.camel.util.jsse.KeyStoreParameters; +import org.w3c.dom.Node; /** * An expression for constructing the different possible {@link org.apache.camel.spi.DataFormat} @@ -272,6 +272,65 @@ public class DataFormatClause<T extends ProcessorDefinition<?>> { } /** + * Uses the MIME Multipart data format + */ + public T mimeMultipart() { + MimeMultipartDataFormat mm = new MimeMultipartDataFormat(); + return dataFormat(mm); + } + + /** + * Uses the MIME Multipart data format + * + * @param multipartSubType Specifies the subtype of the MIME Multipart + */ + public T mimeMultipart(String multipartSubType) { + MimeMultipartDataFormat mm = new MimeMultipartDataFormat(); + mm.setMultipartSubType(multipartSubType); + return dataFormat(mm); + } + + /** + * Uses the MIME Multipart data format + * + * @param multipartSubType the subtype of the MIME Multipart + * @param multipartWithoutAttachment defines whether a message without attachment is also marshaled + * into a MIME Multipart (with only one body part). + * @param headersInline define the MIME Multipart headers as part of the message body + * or as Camel headers + * @param binaryContent have binary encoding for binary content (true) or use Base-64 + * encoding for binary content (false) + */ + public T mimeMultipart(String multipartSubType, boolean multipartWithoutAttachment, boolean headersInline, + boolean binaryContent) { + MimeMultipartDataFormat mm = new MimeMultipartDataFormat(); + mm.setMultipartSubType(multipartSubType); + mm.setMultipartWithoutAttachment(multipartWithoutAttachment); + mm.setHeadersInline(headersInline); + mm.setBinaryContent(binaryContent); + return dataFormat(mm); + } + + /** + * Uses the MIME Multipart data format + * + * @param multipartWithoutAttachment defines whether a message without attachment is also marshaled + * into a MIME Multipart (with only one body part). + * @param headersInline define the MIME Multipart headers as part of the message body + * or as Camel headers + * @param binaryContent have binary encoding for binary content (true) or use Base-64 + * encoding for binary content (false) + */ + public T mimeMultipart(boolean multipartWithoutAttachment, boolean headersInline, + boolean binaryContent) { + MimeMultipartDataFormat mm = new MimeMultipartDataFormat(); + mm.setMultipartWithoutAttachment(multipartWithoutAttachment); + mm.setHeadersInline(headersInline); + mm.setBinaryContent(binaryContent); + return dataFormat(mm); + } + + /** * Uses the PGP data format */ public T pgp(String keyFileName, String keyUserid) { http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/camel-core/src/main/java/org/apache/camel/model/dataformat/DataFormatsDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/dataformat/DataFormatsDefinition.java b/camel-core/src/main/java/org/apache/camel/model/dataformat/DataFormatsDefinition.java index d0ea53b..bdb8caf 100644 --- a/camel-core/src/main/java/org/apache/camel/model/dataformat/DataFormatsDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/dataformat/DataFormatsDefinition.java @@ -57,6 +57,7 @@ public class DataFormatsDefinition { @XmlElement(required = false, name = "jaxb", type = JaxbDataFormat.class), @XmlElement(required = false, name = "jibx", type = JibxDataFormat.class), @XmlElement(required = false, name = "json", type = JsonDataFormat.class), + @XmlElement(required = false, name = "mimeMultipart", type = MimeMultipartDataFormat.class), @XmlElement(required = false, name = "protobuf", type = ProtobufDataFormat.class), @XmlElement(required = false, name = "rss", type = RssDataFormat.class), @XmlElement(required = false, name = "secureXML", type = XMLSecurityDataFormat.class), http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/camel-core/src/main/java/org/apache/camel/model/dataformat/MimeMultipartDataFormat.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/dataformat/MimeMultipartDataFormat.java b/camel-core/src/main/java/org/apache/camel/model/dataformat/MimeMultipartDataFormat.java new file mode 100644 index 0000000..7116f39 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/model/dataformat/MimeMultipartDataFormat.java @@ -0,0 +1,124 @@ +/** + * 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.model.dataformat; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +import org.apache.camel.CamelContext; +import org.apache.camel.model.DataFormatDefinition; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Metadata; + +/** + * MIME Multipart data format + */ +@Metadata(label = "dataformat,transformation", title = "MIME Multipart") +@XmlRootElement(name = "mime-multipart") +@XmlAccessorType(XmlAccessType.FIELD) +public class MimeMultipartDataFormat extends DataFormatDefinition { + + @XmlAttribute + @Metadata(defaultValue = "mixed") + private String multipartSubType = "mixed"; + @XmlAttribute + @Metadata(defaultValue = "false") + private Boolean multipartWithoutAttachment; + @XmlAttribute + @Metadata(defaultValue = "false") + private Boolean headersInline; + @XmlAttribute + @Metadata(defaultValue = "false") + private Boolean binaryContent; + + public MimeMultipartDataFormat() { + super("mime-multipart"); + } + + @Override + protected void configureDataFormat(DataFormat dataFormat, CamelContext camelContext) { + if (getMultipartSubType() != null) { + setProperty(camelContext, dataFormat, "multipartSubType", getMultipartSubType()); + } + if (getMultipartWithoutAttachment() != null) { + setProperty(camelContext, dataFormat, "multipartWithoutAttachment", getMultipartWithoutAttachment()); + } + if (getHeadersInline() != null) { + setProperty(camelContext, dataFormat, "headersInline", getHeadersInline()); + } + if (getBinaryContent() != null) { + setProperty(camelContext, dataFormat, "binaryContent", getBinaryContent()); + } + } + + public String getMultipartSubType() { + return multipartSubType; + } + + /** + * Specify the subtype of the MIME Multipart. + * <p> + * Default is "mixed". + */ + public void setMultipartSubType(String multipartSubType) { + this.multipartSubType = multipartSubType; + } + + public Boolean getMultipartWithoutAttachment() { + return multipartWithoutAttachment; + } + + /** + * Defines whether a message without attachment is also marshaled into a + * MIME Multipart (with only one body part). + * <p> + * Default is "false". + */ + public void setMultipartWithoutAttachment(Boolean multipartWithoutAttachment) { + this.multipartWithoutAttachment = multipartWithoutAttachment; + } + + public Boolean getHeadersInline() { + return headersInline; + } + + /** + * Defines whether the MIME-Multipart headers are part of the message body + * (true) or are set as Camel headers (false). + * <p> + * Default is "false". + */ + public void setHeadersInline(Boolean headersInline) { + this.headersInline = headersInline; + } + + public Boolean getBinaryContent() { + return binaryContent; + } + + /** + * Defines whether the content of binary parts in the MIME multipart is + * binary (true) or Base-64 encoded (false) + * <p> + * Default is "false". + */ + public void setBinaryContent(Boolean binaryContent) { + this.binaryContent = binaryContent; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/camel-core/src/main/resources/org/apache/camel/model/dataformat/jaxb.index ---------------------------------------------------------------------- diff --git a/camel-core/src/main/resources/org/apache/camel/model/dataformat/jaxb.index b/camel-core/src/main/resources/org/apache/camel/model/dataformat/jaxb.index index 17a947c..07253ec 100644 --- a/camel-core/src/main/resources/org/apache/camel/model/dataformat/jaxb.index +++ b/camel-core/src/main/resources/org/apache/camel/model/dataformat/jaxb.index @@ -32,6 +32,7 @@ JaxbDataFormat JibxDataFormat JsonDataFormat JsonLibrary +MimeMultipartDataFormat ProtobufDataFormat RssDataFormat SerializationDataFormat http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/components/camel-mail/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-mail/pom.xml b/components/camel-mail/pom.xml index 5069c05..d5123ce 100644 --- a/components/camel-mail/pom.xml +++ b/components/camel-mail/pom.xml @@ -30,7 +30,7 @@ <description>Camel Mail support</description> <properties> - <camel.osgi.export.pkg>org.apache.camel.component.mail.*</camel.osgi.export.pkg> + <camel.osgi.export.pkg>org.apache.camel.component.mail.*,org.apache.camel.dataformat.mime.multipart.*</camel.osgi.export.pkg> <camel.osgi.export.service> org.apache.camel.spi.ComponentResolver;component=imap, org.apache.camel.spi.ComponentResolver;component=imaps, @@ -38,7 +38,8 @@ org.apache.camel.spi.ComponentResolver;component=pop3, org.apache.camel.spi.ComponentResolver;component=pop3s, org.apache.camel.spi.ComponentResolver;component=smtp, - org.apache.camel.spi.ComponentResolver;component=smtps + org.apache.camel.spi.ComponentResolver;component=smtps, + org.apache.camel.spi.DataFormatResolver;dataformat=mime-multipart </camel.osgi.export.service> </properties> http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/components/camel-mail/src/main/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormat.java ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/main/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormat.java b/components/camel-mail/src/main/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormat.java new file mode 100644 index 0000000..7d6cf1c --- /dev/null +++ b/components/camel-mail/src/main/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormat.java @@ -0,0 +1,241 @@ +/** + * 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.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +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.Exchange; +import org.apache.camel.Message; +import org.apache.camel.NoTypeConversionAvailableException; +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 String multipartSubType = "mixed"; + private boolean multipartWithoutAttachment; + private boolean headersInline; + private boolean binaryContent; + + public void setBinaryContent(boolean binaryContent) { + this.binaryContent = binaryContent; + } + + public void setHeadersInline(boolean headersInline) { + this.headersInline = headersInline; + } + + 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, DataHandler> entry : exchange.getIn().getAttachments().entrySet()) { + String attachmentFilename = entry.getKey(); + DataHandler handler = entry.getValue(); + part = new MimeBodyPart(); + part.setDataHandler(handler); + part.setFileName(MimeUtility.encodeText(attachmentFilename, "UTF-8", null)); + String ct = handler.getContentType(); + contentType = new ContentType(ct); + part.setHeader(CONTENT_TYPE, ct); + if (!contentType.match("text/*") && binaryContent) { + part.setHeader(CONTENT_TRANSFER_ENCODING, "binary"); + } + mp.addBodyPart(part); + exchange.getOut().removeAttachment(attachmentFilename); + } + mm.setContent(mp); + 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); + } 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); + camelMessage.addAttachment(MimeUtility.decodeText(bp.getFileName()), bp.getDataHandler()); + } + } + if (content instanceof BodyPart) { + BodyPart bp = (BodyPart) content; + camelMessage.setBody(bp.getInputStream()); + contentType = bp.getContentType(); + } else { + // Last fallback: I don't see how this can happen, but we do this + // just to be safe + camelMessage.setBody(content); + } + 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)); + } + } + 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); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/components/camel-mail/src/main/resources/META-INF/services/org/apache/camel/dataformat/mime-multipart ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/main/resources/META-INF/services/org/apache/camel/dataformat/mime-multipart b/components/camel-mail/src/main/resources/META-INF/services/org/apache/camel/dataformat/mime-multipart new file mode 100644 index 0000000..b8483d2 --- /dev/null +++ b/components/camel-mail/src/main/resources/META-INF/services/org/apache/camel/dataformat/mime-multipart @@ -0,0 +1,18 @@ +# +# 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. +# + +class=org.apache.camel.dataformat.mime.multipart.MimeMultipartDataFormat http://git-wip-us.apache.org/repos/asf/camel/blob/08aa98f8/components/camel-mail/src/test/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormatTest.java ---------------------------------------------------------------------- diff --git a/components/camel-mail/src/test/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormatTest.java b/components/camel-mail/src/test/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormatTest.java new file mode 100644 index 0000000..7dbfe42 --- /dev/null +++ b/components/camel-mail/src/test/java/org/apache/camel/dataformat/mime/multipart/MimeMultipartDataFormatTest.java @@ -0,0 +1,275 @@ +/** + * 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 javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.util.ByteArrayDataSource; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultExchange; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.IOHelper; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.IsCollectionContaining.hasItem; +import static org.hamcrest.core.StringStartsWith.startsWith; + +public class MimeMultipartDataFormatTest extends CamelTestSupport { + private Exchange exchange; + private Message in; + + @Before + public void setUp() throws Exception { + super.setUp(); + exchange = new DefaultExchange(context); + in = exchange.getIn(); + } + + @Test + public void roundtripWithTextAttachments() throws IOException { + String attContentType = "text/plain"; + String attText = "Attachment Text"; + String attFileName = "Attachment File Name"; + in.setBody("Body text"); + in.setHeader(Exchange.CONTENT_TYPE, "text/plain;charset=iso8859-1;other-parameter=true"); + in.setHeader(Exchange.CONTENT_ENCODING, "UTF8"); + addAttachment(attContentType, attText, attFileName); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertThat(out.getHeader(Exchange.CONTENT_TYPE, String.class), startsWith("text/plain")); + assertEquals("UTF8", out.getHeader(Exchange.CONTENT_ENCODING)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertEquals(attText, new String(os.toByteArray())); + } + + @Test + public void roundtripWithTextAttachmentsHeadersInline() throws IOException { + String attContentType = "text/plain"; + String attText = "Attachment Text"; + String attFileName = "Attachment File Name"; + in.setBody("Body text"); + in.setHeader(Exchange.CONTENT_TYPE, "text/plain;charset=iso8859-1;other-parameter=true"); + in.setHeader(Exchange.CONTENT_ENCODING, "UTF8"); + addAttachment(attContentType, attText, attFileName); + Exchange result = template.send("direct:roundtripinlineheaders", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertThat(out.getHeader(Exchange.CONTENT_TYPE, String.class), startsWith("text/plain")); + assertEquals("UTF8", out.getHeader(Exchange.CONTENT_ENCODING)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertEquals(attText, new String(os.toByteArray())); + } + + @Test + public void roundtripWithTextAttachmentsAndSpecialCharacters() throws IOException { + String attContentType = "text/plain"; + String attText = "Attachment Text with special characters: \u00A9"; + String attFileName = "Attachment File Name with special characters: \u00A9"; + in.setBody("Body text with special characters: \u00A9"); + in.setHeader(Exchange.CONTENT_TYPE, "text/plain"); + in.setHeader(Exchange.CONTENT_ENCODING, "UTF8"); + addAttachment(attContentType, attText, attFileName); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text with special characters: \u00A9", out.getBody(String.class)); + assertThat(out.getHeader(Exchange.CONTENT_TYPE, String.class), startsWith("text/plain")); + assertEquals("UTF8", out.getHeader(Exchange.CONTENT_ENCODING)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertEquals(attText, new String(os.toByteArray())); + } + + @Test + public void roundtripWithTextAttachmentsAndBinaryContent() throws IOException { + String attContentType = "text/plain"; + String attText = "Attachment Text"; + String attFileName = "Attachment File Name"; + in.setBody("Body text"); + in.setHeader(Exchange.CONTENT_TYPE, "text/plain;charset=iso8859-1;other-parameter=true"); + in.setHeader(Exchange.CONTENT_ENCODING, "UTF8"); + addAttachment(attContentType, attText, attFileName); + Exchange result = template.send("direct:roundtripbinarycontent", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertThat(out.getHeader(Exchange.CONTENT_TYPE, String.class), startsWith("text/plain")); + assertEquals("UTF8", out.getHeader(Exchange.CONTENT_ENCODING)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertEquals(attText, new String(os.toByteArray())); + } + + @Test + public void roundtripWithBinaryAttachments() throws IOException { + String attContentType = "application/binary"; + byte[] attText = {0, 1, 2, 3, 4, 5, 6, 7}; + String attFileName = "Attachment File Name"; + in.setBody("Body text"); + DataSource ds = new ByteArrayDataSource(attText, attContentType); + in.addAttachment(attFileName, new DataHandler(ds)); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertArrayEquals(attText, os.toByteArray()); + } + + @Test + public void roundtripWithBinaryAttachmentsAndBinaryContent() throws IOException { + String attContentType = "application/binary"; + byte[] attText = {0, 1, 2, 3, 4, 5, 6, 7}; + String attFileName = "Attachment File Name"; + in.setBody("Body text"); + DataSource ds = new ByteArrayDataSource(attText, attContentType); + in.addAttachment(attFileName, new DataHandler(ds)); + Exchange result = template.send("direct:roundtripbinarycontent", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertTrue(out.hasAttachments()); + assertEquals(1, out.getAttachmentNames().size()); + assertThat(out.getAttachmentNames(), hasItem(attFileName)); + DataHandler dh = out.getAttachment(attFileName); + assertNotNull(dh); + assertEquals(attContentType, dh.getContentType()); + InputStream is = dh.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IOHelper.copyAndCloseInput(is, os); + assertArrayEquals(attText, os.toByteArray()); + } + + @Test + public void roundtripWithoutAttachments() throws IOException { + in.setBody("Body text"); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertFalse(out.hasAttachments()); + } + + @Test + public void roundtripWithoutAttachmentsToMultipart() throws IOException { + in.setBody("Body text"); + Exchange result = template.send("direct:roundtripmultipart", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertFalse(out.hasAttachments()); + } + + @Test + public void roundtripWithoutAttachmentsAndContentType() throws IOException { + in.setBody("Body text"); + in.setHeader("Content-Type", "text/plain"); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertFalse(out.hasAttachments()); + } + + @Test + public void roundtripWithoutAttachmentsAndInvalidContentType() throws IOException { + in.setBody("Body text"); + in.setHeader("Content-Type", "text?plain"); + Exchange result = template.send("direct:roundtrip", exchange); + Message out = result.getOut(); + assertEquals("Body text", out.getBody(String.class)); + assertFalse(out.hasAttachments()); + } + + @Test + public void marhsalOnlyMixed() throws IOException { + in.setBody("Body text"); + in.setHeader("Content-Type", "text/plain"); + addAttachment("application/octet-stream", "foobar", "attachment.bin"); + Exchange result = template.send("direct:marshalonlymixed", exchange); + assertThat(result.getOut().getHeader("Content-Type", String.class), startsWith("multipart/mixed")); + } + + @Test + public void marhsalOnlyRelated() throws IOException { + in.setBody("Body text"); + in.setHeader("Content-Type", "text/plain"); + addAttachment("application/octet-stream", "foobar", "attachment.bin"); + Exchange result = template.send("direct:marshalonlyrelated", exchange); + assertThat(result.getOut().getHeader("Content-Type", String.class), startsWith("multipart/related")); + } + + private void addAttachment(String attContentType, String attText, String attFileName) throws IOException { + DataSource ds = new ByteArrayDataSource(attText, attContentType); + in.addAttachment(attFileName, new DataHandler(ds)); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:roundtrip").marshal().mimeMultipart().to("log:mime?showHeaders=true").unmarshal().mimeMultipart(); + from("direct:roundtripmultipart").marshal().mimeMultipart(true, false, false).to("log:mime?showHeaders=true").unmarshal().mimeMultipart(); + from("direct:roundtripinlineheaders").marshal().mimeMultipart(false, true, false).to("log:mime?showHeaders=true").unmarshal().mimeMultipart(false, true, false); + from("direct:roundtripbinarycontent").marshal().mimeMultipart(false, false, true).to("log:mime?showHeaders=true").to("dataformat:mime-multipart:unmarshal"); + from("direct:marshalonlyrelated").marshal().mimeMultipart("related"); + from("direct:marshalonlymixed").marshal().mimeMultipart(); + } + }; + } +}