JAMES-2159 Introduce ContactExtractor mailet

Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/dfdb2d0a
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/dfdb2d0a
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/dfdb2d0a

Branch: refs/heads/master
Commit: dfdb2d0a9ab733e8956a48845f9a292ab17ab240
Parents: 4c5cc6a
Author: Matthieu Baechler <matth...@apache.org>
Authored: Fri Sep 22 09:48:40 2017 +0200
Committer: Antoine Duprat <adup...@linagora.com>
Committed: Wed Sep 27 16:09:13 2017 +0200

----------------------------------------------------------------------
 mailet/standard/pom.xml                         |  13 ++
 .../transport/mailets/ContactExtractor.java     | 130 +++++++++++++++
 .../transport/mailets/ContactExtractorTest.java | 142 ++++++++++++++++
 server/mailet/integration-testing/pom.xml       |   6 +
 .../transport/mailets/ContactExtractorTest.java | 166 +++++++++++++++++++
 5 files changed, 457 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/dfdb2d0a/mailet/standard/pom.xml
----------------------------------------------------------------------
diff --git a/mailet/standard/pom.xml b/mailet/standard/pom.xml
index 9ebfd19..4c52812 100644
--- a/mailet/standard/pom.xml
+++ b/mailet/standard/pom.xml
@@ -57,6 +57,14 @@
             <artifactId>james-server-util-java8</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jdk8</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
@@ -78,6 +86,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-fluent</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dfdb2d0a/mailet/standard/src/main/java/org/apache/james/transport/mailets/ContactExtractor.java
----------------------------------------------------------------------
diff --git 
a/mailet/standard/src/main/java/org/apache/james/transport/mailets/ContactExtractor.java
 
b/mailet/standard/src/main/java/org/apache/james/transport/mailets/ContactExtractor.java
new file mode 100644
index 0000000..468d355
--- /dev/null
+++ 
b/mailet/standard/src/main/java/org/apache/james/transport/mailets/ContactExtractor.java
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.james.transport.mailets;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.Mailet;
+import org.apache.mailet.MailetException;
+import org.apache.mailet.base.GenericMailet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * <p>Collects the sender and the recipients of a message and store them as 
JSON in a specified message attribute.</p>
+ * <p>Here is the JSON format:</p>
+ * <pre><code>
+ * {
+ *   "userEmail" : "sen...@james.org", 
+ *   "emails" : [ "t...@james.org", "c...@james.org" ]
+ * }
+ * </code></pre>
+ * 
+ * <p>Sample configuration:</p>
+ *
+ * <pre><code>
+ * &lt;mailet match="All" class="ContactExtractor"&gt;
+ *   &lt;attribute&gt;ExtractedContacts&lt;/attribute&gt;
+ * &lt;/mailet&gt;
+ * </code></pre>
+ */
+public class ContactExtractor extends GenericMailet implements Mailet {
+
+    public interface Configuration {
+        String ATTRIBUTE = "attribute";
+    }
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ContactExtractor.class);
+
+    @VisibleForTesting ObjectMapper objectMapper;
+    private String extractAttributeTo;
+
+    @Override
+    public void init() throws MessagingException {
+        extractAttributeTo = 
getInitParameterAsOptional(Configuration.ATTRIBUTE)
+                .orElseThrow(() -> new MailetException("No value for " + 
Configuration.ATTRIBUTE + " parameter was provided."));
+
+        objectMapper = new ObjectMapper().registerModule(new Jdk8Module());
+    }
+
+    @Override
+    public String getMailetInfo() {
+        return "ContactExtractor Mailet" ;
+    }
+
+    @Override
+    public void service(Mail mail) throws MessagingException {
+        try {
+            Optional<String> payload = extractContacts(mail.getMessage());
+            LOGGER.debug("payload : {}", payload);
+            payload.ifPresent(x -> mail.setAttribute(extractAttributeTo, x));
+        } catch (Exception e) {
+            LOGGER.error("Error while extracting contacts", e);
+        }
+    }
+
+    private Optional<String> extractContacts(MimeMessage mimeMessage) throws 
MessagingException {
+        return Optional.ofNullable(mimeMessage.getSender())
+                .map(Address::toString)
+                .filter(Throwing.predicate(sender -> 
hasRecipients(mimeMessage)))
+                .map(Throwing.function(sender -> new ExtractedContacts(sender, 
recipients(mimeMessage))))
+                .map(Throwing.function(message -> 
objectMapper.writeValueAsString(message)));
+    }
+
+    private boolean hasRecipients(MimeMessage mimeMessage) throws 
MessagingException {
+        return mimeMessage.getAllRecipients().length > 0;
+    }
+
+    private ImmutableList<String> recipients(MimeMessage mimeMessage) throws 
MessagingException {
+        return Arrays.stream(mimeMessage.getAllRecipients())
+                .map(Address::toString)
+                .collect(Guavate.toImmutableList());
+    }
+
+    public static class ExtractedContacts {
+        private final String userEmail;
+        private final ImmutableList<String> emails;
+
+        public ExtractedContacts(String userEmail, ImmutableList<String> 
emails) {
+            this.emails = emails;
+            this.userEmail = userEmail;
+        }
+
+        public ImmutableList<String> getEmails() {
+            return emails;
+        }
+
+        public String getUserEmail() {
+            return userEmail;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dfdb2d0a/mailet/standard/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
----------------------------------------------------------------------
diff --git 
a/mailet/standard/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
 
b/mailet/standard/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
new file mode 100644
index 0000000..9eda1f2
--- /dev/null
+++ 
b/mailet/standard/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
@@ -0,0 +1,142 @@
+/****************************************************************
+ * 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.james.transport.mailets;
+
+import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.MailetContext;
+import org.apache.mailet.MailetException;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailContext;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.apache.mailet.base.test.MimeMessageBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ContactExtractorTest {
+
+    private static final String ATTRIBUTE = "ExtractedContacts";
+    private static final String SENDER = "sen...@james.org";
+    private static final String TO = "t...@james.org";
+
+    private ContactExtractor mailet;
+    private MailetContext mailetContext;
+    private FakeMailetConfig mailetConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        mailet = new ContactExtractor();
+        mailetContext = FakeMailContext.builder()
+                .build();
+        mailetConfig = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(mailetContext)
+                .setProperty(ContactExtractor.Configuration.ATTRIBUTE, 
ATTRIBUTE)
+                .build();
+    }
+
+    @Test
+    public void initShouldThrowWhenNoAttributeParameter() throws 
MessagingException {
+        FakeMailetConfig customMailetConfig = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(mailetContext)
+                .build();
+        
+        assertThatThrownBy(() -> mailet.init(customMailetConfig))
+            .isInstanceOf(MailetException.class);
+    }
+
+    @Test
+    public void initShouldNotThrowWithAllParameters() throws 
MessagingException {
+        mailet.init(mailetConfig);
+    }
+
+    @Test
+    public void getMailetInfoShouldReturnInfo() {
+        assertThat(mailet.getMailetInfo()).isEqualTo("ContactExtractor 
Mailet");
+    }
+
+    @Test
+    public void serviceShouldNotThrowWhenJsonProcessingFails() throws 
Exception {
+        FakeMail mail = 
FakeMail.builder().mimeMessage(MimeMessageBuilder.defaultMimeMessage())
+                .sender(new MailAddress(SENDER))
+                .recipient(new MailAddress(TO))
+                .build();
+
+        ObjectMapper objectMapper = mock(ObjectMapper.class);
+        
when(objectMapper.writeValueAsString(any(ContactExtractor.ExtractedContacts.class)))
+            .thenThrow(new JsonGenerationException(""));
+
+        mailet.init(mailetConfig);
+        mailet.objectMapper = objectMapper;
+
+        mailet.service(mail);
+    }
+
+    @Test
+    public void serviceShouldAddTheAttribute() throws Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+                .setSender(SENDER)
+                .addToRecipient(TO)
+                .setSubject("Contact collection Rocks")
+                .setText("This is my email")
+                .build();
+        FakeMail mail = FakeMail.builder().mimeMessage(message)
+            .sender(new MailAddress(SENDER))
+            .recipient(new MailAddress(TO))
+            .build();
+        mailet.init(mailetConfig);
+
+        String expectedMessage = "{\"userEmail\" : \"" + SENDER + "\", 
\"emails\" : [ \"" + TO + "\" ]}";
+        mailet.service(mail);
+
+        
assertThatJson(mail.getAttribute(ATTRIBUTE).toString()).isEqualTo(expectedMessage);
+
+    }
+
+    @Test
+    public void serviceShouldNotAddTheAttributeWhenNoRecipient() throws 
Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+                .setSender(SENDER)
+                .setSubject("Contact collection Rocks")
+                .setText("This is my email")
+                .build();
+        FakeMail mail = FakeMail.builder().mimeMessage(message)
+            .sender(new MailAddress(SENDER))
+            .build();
+        mailet.init(mailetConfig);
+
+        mailet.service(mail);
+
+        assertThat(mail.getAttribute(ATTRIBUTE)).isNull();
+
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/dfdb2d0a/server/mailet/integration-testing/pom.xml
----------------------------------------------------------------------
diff --git a/server/mailet/integration-testing/pom.xml 
b/server/mailet/integration-testing/pom.xml
index 02858f8..c2359ee 100644
--- a/server/mailet/integration-testing/pom.xml
+++ b/server/mailet/integration-testing/pom.xml
@@ -130,6 +130,12 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-fluent</artifactId>
+            <version>1.5.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dfdb2d0a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
 
b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
new file mode 100644
index 0000000..16eeb89
--- /dev/null
+++ 
b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ContactExtractorTest.java
@@ -0,0 +1,166 @@
+/****************************************************************
+ * 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.james.transport.mailets;
+
+import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.jmap.mailet.VacationMailet;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.transport.mailets.amqp.AmqpRule;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.transport.matchers.RecipientIsLocal;
+import org.apache.james.transport.matchers.SMTPAuthSuccessful;
+import org.apache.james.util.streams.SwarmGenericContainer;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.IMAPMessageReader;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.MimeMessageBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import com.jayway.awaitility.Awaitility;
+import com.jayway.awaitility.Duration;
+
+public class ContactExtractorTest {
+
+    public static final String JAMES_ORG = "james.org";
+    public static final String SENDER = "sender@" + JAMES_ORG;
+    public static final String TO = "to@" + JAMES_ORG;
+    public static final String TO2 = "to2@" + JAMES_ORG;
+    public static final String CC = "cc@" + JAMES_ORG;
+    public static final String CC2 = "cc2@" + JAMES_ORG;
+    public static final String BCC = "bcc@" + JAMES_ORG;
+    public static final String BCC2 = "bcc2@" + JAMES_ORG;
+    public static final String PASSWORD = "secret";
+    public static final String EXCHANGE = "collector:email";
+    public static final String ROUTING_KEY = "";
+
+    public SwarmGenericContainer rabbit = new 
SwarmGenericContainer("rabbitmq:3");
+    public AmqpRule amqpRule = new AmqpRule(rabbit, EXCHANGE, ROUTING_KEY);
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    @Rule
+    public RuleChain chain = 
RuleChain.outerRule(rabbit).around(amqpRule).around(folder);
+
+    private TemporaryJamesServer jamesServer;
+
+    @Before
+    public void setup() throws Exception {
+        String attribute = "ExtractedContacts";
+        MailetContainer mailets = MailetContainer
+            .builder()
+            .threads(5)
+            .postmaster(SENDER)
+            .addProcessor(CommonProcessors.root())
+            .addProcessor(CommonProcessors.error())
+            .addProcessor(
+                ProcessorConfiguration.builder()
+                    .state("transport")
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(SMTPAuthSuccessful.class)
+                        .mailet(ContactExtractor.class)
+                        .addProperty(ContactExtractor.Configuration.ATTRIBUTE, 
attribute)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(AmqpForwardAttribute.class)
+                        .addProperty(AmqpForwardAttribute.URI_PARAMETER_NAME, 
amqpRule.getAmqpUri())
+                        
.addProperty(AmqpForwardAttribute.EXCHANGE_PARAMETER_NAME, EXCHANGE)
+                        
.addProperty(AmqpForwardAttribute.ATTRIBUTE_PARAMETER_NAME, attribute)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(RemoveMimeHeader.class)
+                        .addProperty("name", "bcc")
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIsLocal.class)
+                        .mailet(VacationMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIsLocal.class)
+                        .mailet(LocalDelivery.class)
+                        .build())
+                    .build())
+            .build();
+        jamesServer = new TemporaryJamesServer(folder, mailets);
+        DataProbeImpl probe = jamesServer.getProbe(DataProbeImpl.class);
+        probe.addDomain(JAMES_ORG);
+        probe.addUser(SENDER, PASSWORD);
+        probe.addUser(TO, PASSWORD);
+        probe.addUser(TO2, PASSWORD);
+        probe.addUser(CC, PASSWORD);
+        probe.addUser(CC2, PASSWORD);
+        probe.addUser(BCC, PASSWORD);
+        probe.addUser(BCC2, PASSWORD);
+    }
+
+    @After
+    public void tearDown() {
+        jamesServer.shutdown();
+    }
+
+    @Test
+    public void recipientsShouldBePublishedToAmqpWhenSendingEmail() throws 
Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+            .setSender(SENDER)
+            .addToRecipient(TO, "John To2 <" + TO2 + ">")
+            .addCcRecipient(CC, "John Cc2 <" + CC2 + ">")
+            .addBccRecipient(BCC, "John Bcc2 <" + BCC2 + ">")
+            .setSubject("Contact collection Rocks")
+            .setText("This is my email")
+            .build();
+        FakeMail mail = FakeMail.builder()
+            .mimeMessage(message)
+            .sender(new MailAddress(SENDER))
+            .recipients(new MailAddress(TO), new MailAddress(TO2), new 
MailAddress(CC), new MailAddress(CC2), new MailAddress(BCC), new 
MailAddress(BCC2))
+            .build();
+        try (SMTPMessageSender messageSender = 
SMTPMessageSender.authentication("localhost", 1025, JAMES_ORG, SENDER, 
PASSWORD);
+                IMAPMessageReader imap = new IMAPMessageReader("localhost", 
1143)) {
+
+            messageSender.sendMessage(mail);
+            Awaitility.await().pollDelay(Duration.FIVE_HUNDRED_MILLISECONDS)
+                .atMost(Duration.ONE_MINUTE)
+                .until(() -> imap.userReceivedMessage(TO, PASSWORD));
+
+            Optional<String> actual = amqpRule.readContent();
+            assertThat(actual).isNotEmpty();
+            assertThatJson(actual.get()).isEqualTo("{"
+                    + "\"userEmail\" : \"sen...@james.org\", "
+                    + "\"emails\" : [ \"t...@james.org\", \"John To2 
<t...@james.org>\", \"c...@james.org\", \"John Cc2 <c...@james.org>\", 
\"b...@james.org\", \"John Bcc2 <b...@james.org>\" ]"
+                    + "}");
+        }
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to