Если вы используете компонент Camel-Mail для обработки некоторой бизнес-логики, которая включает получение электронной почты, содержащей вложения, то вам может быть интересно, как эти вложения электронной почты можно разделить на отдельные сообщения, чтобы они могли обрабатываться индивидуально. В этом посте будет продемонстрировано, как это можно сделать с помощью выражения Camel Expression и JUnit-теста, демонстрирующего такое поведение.
Недавно Клаус Исбен , коммиттер Apache Camel, добавил на страницу компонента Apache Camel Mail новую документацию, в которой создается выражение для разделения каждого вложения в обмене на отдельное сообщение. Кроме того, он включил этот код в Camel 2.10, и он доступен как org.apache.camel.component.mail.SplitAttachmentExpression, Этот класс использует класс ExpressionAdapter, который в Camel 2.9 доступен как org.apache.camel.support.ExpressionAdpater, а для Camel 2.8 и более ранних версий доступен как org.apache.camel.impl.ExpressionAdapter .
Давайте посмотрим на выражение 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; } }
Из приведенного выше кода вы можете видеть, что Expression разбивает обмен на отдельные сообщения, каждое из которых содержит одно вложение, хранящееся в объекте List, который затем возвращается в среду выполнения Camel, которую затем можно использовать для перебора сообщений. Для получения дополнительной информации о том, как работает EIP сплиттера, см. Документацию Camel Splitter EIP .
Теперь мы можем протестировать это выражение с помощью следующего контрольного примера JUnit и убедиться, что вложения действительно разбиты на отдельные сообщения для обработки:
/** * 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://[email protected]?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://[email protected]?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(); } }; } }
На маршруте вы можете увидеть, что разлитый объект использует выражение AttachmentsExpression, которое было показано выше. Кроме того, я использую простой процессор для установки заголовка биржи, который содержит название вложения. Затем, используя CBR (контентный маршрутизатор), обмен будет направлен в конечную точку на основе прикрепленного файла. В тестовом примере используются две фиктивные конечные точки, которые используются для проверки тела сообщения, количества вложений, имени вложения и типа вложения.
Следующий код был использован в MyMailProcessor для установки заголовка:
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"); } }
Создать новый проект Maven, чтобы проверить это на себе, очень легко. Просто создайте новый проект maven, используя один из доступных архетипов Camel maven , camel-archetype-java должен прекрасно работать для этого. После создания проекта вам просто нужно скопировать вышеупомянутые классы в проект.
В дополнение к настройке кода вам также необходимо убедиться, что в 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>
Обратите внимание на приведенный выше тестовый пример. У меня был свой собственный класс, в котором реализовано выражение SplitAttachmentExpression, поскольку я делал это до того, как класс был добавлен в базу кода. Если вы используете последнюю версию 2.10, импорт SplitAttachmentExpression должен быть изменен на org.apache.camel.component.mail.SplitAttachmentExpression. Если вы используете более раннюю версию, просто создайте класс, как я делал, используя код сверху.