Recently, Claus Isben, an Apache Camel committer added some new documentation on the Apache Camel Mail component page that creates an Expression to split each attachment in an exchange into a separate message. In addition he has included this code in Camel 2.10 and it is available as org.apache.camel.component.mail.SplitAttachmentExpression. This class is using the ExpressionAdapter class which in Camel 2.9 is available as org.apache.camel.support.ExpressionAdpater and for Camel 2.8 and earlier is available as org.apache.camel.impl.ExpressionAdapter.
Let's have a look at the SplitAttachmentExpression:
/**
* 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.component.mail;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.activation.DataHandler;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.support.ExpressionAdapter;
/**
* A {@link org.apache.camel.Expression} which can be used to split a {@link MailMessage}
* per attachment. For example if a mail message has 5 attachments, then this
* expression will return a List<Message> that contains 5 {@link Message}
* and each have a single attachment from the source {@link MailMessage}.
*/
public class SplitAttachmentsExpression extends ExpressionAdapter {
@Override
public Object evaluate(Exchange exchange) {
// must use getAttachments to ensure attachments is initial populated
if (exchange.getIn().getAttachments().isEmpty()) {
return null;
}
// we want to provide a list of messages with 1 attachment per mail
List<Message> answer = new ArrayList<Message>();
for (Map.Entry<String, DataHandler> entry : exchange.getIn().getAttachments().entrySet()) {
final Message copy = exchange.getIn().copy();
copy.getAttachments().clear();
copy.getAttachments().put(entry.getKey(), entry.getValue());
answer.add(copy);
}
return answer;
}
}
From the above code you can see the Expression splits the exchange into separate messages, each containing one attachment, stored in a List object which is then returned to the Camel runtime which can then be used to iterate through the messages. For more information on how the splitter EIP works see the Camel Splitter EIP documentation.
Now we can test this Expression by using the following JUnit test case and verify that the attachments are indeed split into separate messages for processing:
/**
* 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 com.fusesource.example;
import com.fusesource.example.expression.AttachmentsExpression;
import com.fusesource.example.processor.MyMailProcessor;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Producer;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
import org.jvnet.mock_javamail.Mailbox;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import java.util.Map;
/**
* Unit test for Camel attachments and Mail attachments.
*/
public class MailAttachmentTest extends CamelTestSupport {
private String subject = "Test Camel Mail Route";
@Test
public void testSendAndReceiveMailWithAttachments() throws Exception {
// clear mailbox
Mailbox.clearAll();
// create an exchange with a normal body and attachment to be produced as email
Endpoint endpoint = context.getEndpoint("smtp://james@mymailserver.com?password=secret");
// create the exchange with the mail message that is multipart with a file and a Hello World text/plain message.
Exchange exchange = endpoint.createExchange();
Message in = exchange.getIn();
in.setBody("Hello World");
in.addAttachment("message1.xml", new DataHandler(new FileDataSource("src/data/message1.xml")));
in.addAttachment("message2.xml", new DataHandler(new FileDataSource("src/data/message2.xml")));
// create a producer that can produce the exchange (= send the mail)
Producer producer = endpoint.createProducer();
// start the producer
producer.start();
// and let it go (processes the exchange by sending the email)
producer.process(exchange);
// need some time for the mail to arrive on the inbox (consumed and sent to the mock)
Thread.sleep(5000);
// verify destination1
MockEndpoint destination1 = getMockEndpoint("mock:destination1");
destination1.expectedMessageCount(1);
Exchange destination1Exchange = destination1.assertExchangeReceived(0);
destination1.assertIsSatisfied();
// plain text
assertEquals("Hello World", destination1Exchange.getIn().getBody(String.class));
// attachment
Map destination1Attachments = destination1Exchange.getIn().getAttachments();
assertEquals(1, destination1Attachments.size());
DataHandler d1Attachment = destination1Attachments.get("message1.xml");
assertNotNull("The message1.xml should be there", d1Attachment);
assertEquals("application/octet-stream; name=message1.xml", d1Attachment.getContentType());
assertEquals("Handler name should be the file name", "message1.xml", d1Attachment.getName());
// verify destination2
MockEndpoint destination2 = getMockEndpoint("mock:destination2");
destination2.expectedMessageCount(1);
Exchange destination2Exchange = destination2.assertExchangeReceived(0);
destination2.assertIsSatisfied();
// plain text
assertEquals("Hello World", destination2Exchange.getIn().getBody(String.class));
// attachment
Map destination2Attachments = destination2Exchange.getIn().getAttachments();
assertEquals(1, destination2Attachments.size());
DataHandler d2Attachment = destination2Attachments.get("message2.xml");
assertNotNull("The message2.xml should be there", d2Attachment);
assertEquals("application/octet-stream; name=message2.xml", d2Attachment.getContentType());
assertEquals("Handler name should be the file name", "message2.xml", d2Attachment.getName());
producer.stop();
}
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() throws Exception {
context().setStreamCaching(true);
from("pop3://james@mymailserver.com?password=secret&consumer.delay=1000")
.setHeader("subject", constant(subject))
.split(new AttachmentsExpression())
.process(new MyMailProcessor())
.choice()
.when(header("attachmentName").isEqualTo("message1.xml"))
.to("mock:destination1")
.otherwise()
.to("mock:destination2")
.end();
}
};
}
}
In the route you can see the spilt is using the AttachmentsExpression which was shown above. In addition, I am using a simple processor to set the header of the exchange which contains the name of the attachment. Then, using the CBR (content base router) the exchange will be routed to an endpoint based on the attached file. The test case uses two mock endpoints which are used to validate the body of the message, number of attachments, attachment name, and attachment type.
The following code was used in the MyMailProcessor to set the header:
package com.fusesource.example.processor;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.log4j.Logger;
import javax.activation.DataHandler;
import java.util.Map;
/**
* Created by IntelliJ IDEA.
* User: jsherman
* Date: 4/9/12
* Time: 11:39 AM
* To change this template use File | Settings | File Templates.
*/
public class MyMailProcessor implements Processor {
private static final Logger LOG = Logger
.getLogger(MyMailProcessor.class.getName());
public void process(Exchange exchange) throws Exception {
LOG.debug("MyMailProcessor...");
String body = exchange.getIn().getBody(String.class);
Map<String, DataHandler> attachments = exchange.getIn().getAttachments();
if (attachments.size() > 0) {
for (String name : attachments.keySet()) {
exchange.getOut().setHeader("attachmentName", name);
}
}
// read the attachments from the in exchange putting them back on the out
exchange.getOut().setAttachments(attachments);
// resetting the body on out exchange
exchange.getOut().setBody(body);
LOG.debug("MyMailProcessor complete");
}
}
Setting a up new maven project to test this out for yourself is very easy. Simply create a new maven project using one of the available Camel maven archetypes, the camel-archetype-java should work just fine for this. Once you have the project created you just need to copy the above classes into the project.
In addition to setting up the code you will also need to ensure the following dependencies are included in the project's pom.xml:
<properties>
<camel-version>2.9.0</camel-version>
</properties>
...
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-mail</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<scope>test</scope>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.jvnet.mock-javamail</groupId>
<artifactId>mock-javamail</artifactId>
<version>1.7</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
Note for the above test case I had my own class that implemented the SplitAttachmentExpression, as I was doing this before the class was added into the code base. If you are using the latest 2.10 the SplitAttachmentExpression import should be changed to org.apache.camel.component.mail.SplitAttachmentExpression. If you are using an earlier version, simply create the class as I did using the code from above.
No comments:
Post a Comment