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> + * <mailet match="All" class="ContactExtractor"> + * <attribute>ExtractedContacts</attribute> + * </mailet> + * </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