Статьи

Camel: Работа с вложениями электронной почты

Если вы используете компонент 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://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();  
       }  
     };  
   }  
 }  

На маршруте вы можете увидеть, что разлитый объект использует выражение 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. Если вы используете более раннюю версию, просто создайте класс, как я делал, используя код сверху.