MAILET-150 ICalendarParser looks for ICS, parse it with Ical4J then store it as a mail attribute
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/2e4005bc Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/2e4005bc Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/2e4005bc Branch: refs/heads/master Commit: 2e4005bc233d5f917f7dd56ffa6b9d74710f7569 Parents: c6edcbc Author: Quynh Nguyen <qngu...@linagora.com> Authored: Mon Jan 16 11:46:14 2017 +0700 Committer: Antoine Duprat <adup...@linagora.com> Committed: Wed Jan 18 15:46:51 2017 +0100 ---------------------------------------------------------------------- mailet/icalendar/pom.xml | 6 +- .../transport/mailets/ICalendarParser.java | 128 ++++++++++ .../transport/mailets/ICalendarParserTest.java | 235 +++++++++++++++++++ mailet/pom.xml | 13 +- 4 files changed, 380 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/2e4005bc/mailet/icalendar/pom.xml ---------------------------------------------------------------------- diff --git a/mailet/icalendar/pom.xml b/mailet/icalendar/pom.xml index f63a699..7c35346 100644 --- a/mailet/icalendar/pom.xml +++ b/mailet/icalendar/pom.xml @@ -187,6 +187,10 @@ <artifactId>guava</artifactId> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> @@ -283,4 +287,4 @@ </profiles> -</project> \ No newline at end of file +</project> http://git-wip-us.apache.org/repos/asf/james-project/blob/2e4005bc/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICalendarParser.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICalendarParser.java b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICalendarParser.java new file mode 100644 index 0000000..6930556 --- /dev/null +++ b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICalendarParser.java @@ -0,0 +1,128 @@ +/**************************************************************** + * 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.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.stream.Stream; + +import javax.mail.MessagingException; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMailet; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Calendar; + +/** + * <p> + * This mailet can be combined with the Strip attachment mailet. + * </p> + * <p> + * The ICS body part byte array is arranged as map then this mailet should look for ICS and parse it with Ical4J then store it as a mail attribute + * </p> + * <p> + * Configuration: The mailet contains 2 mandatory attributes + * </p> + * <p> + * + * <pre> + * <mailet match="All" class="ICalendarParser" > + * <sourceAttribute>source.attribute.name</sourceAttribute> <!-- The attribute which contains output value of StripAttachment mailet -- > + * <destAttribute>dest.attribute.name</destAttribute> <!-- The attribute store the map of Calendar -- > + * </mailet > + * + * </pre> + * + * </p> + */ +public class ICalendarParser extends GenericMailet { + public static final String SOURCE_ATTRIBUTE_PARAMETER_NAME = "sourceAttribute"; + public static final String DESTINATION_ATTRIBUTE_PARAMETER_NAME = "destinationAttribute"; + + public static final String SOURCE_ATTRIBUTE_PARAMETER_DEFAULT_VALUE = "icsAttachments"; + public static final String DESTINATION_ATTRIBUTE_PARAMETER_DEFAULT_VALUE = "calendars"; + + private String sourceAttributeName; + private String destinationAttributeName; + + @Override + public void init() throws MessagingException { + sourceAttributeName = getInitParameter(SOURCE_ATTRIBUTE_PARAMETER_NAME, SOURCE_ATTRIBUTE_PARAMETER_DEFAULT_VALUE); + if (Strings.isNullOrEmpty(sourceAttributeName)) { + throw new MessagingException("source attribute cannot be empty"); + } + destinationAttributeName = getInitParameter(DESTINATION_ATTRIBUTE_PARAMETER_NAME, DESTINATION_ATTRIBUTE_PARAMETER_DEFAULT_VALUE); + if (Strings.isNullOrEmpty(destinationAttributeName)) { + throw new MessagingException("destination attribute cannot be empty"); + } + } + + @VisibleForTesting + public String getSourceAttributeName() { + return sourceAttributeName; + } + + @VisibleForTesting + public String getDestinationAttributeName() { + return destinationAttributeName; + } + + @Override + public void service(Mail mail) throws MessagingException { + Object icsAttachmentsObj = mail.getAttribute(sourceAttributeName); + if (icsAttachmentsObj == null || !(icsAttachmentsObj instanceof Map)) { + return; + } + + Map<String, byte[]> icsAttachments = (Map<String, byte[]>) icsAttachmentsObj; + Map<String, Calendar> calendars = icsAttachments.entrySet() + .stream() + .flatMap(entry -> createCalendar(entry.getKey(), entry.getValue())) + .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue)); + + mail.setAttribute(destinationAttributeName, (Serializable) calendars); + } + + @Override + public String getMailetInfo() { + return "Calendar Parser"; + } + + private Stream<Pair<String, Calendar>> createCalendar(String key, byte[] icsContent) { + CalendarBuilder builder = new CalendarBuilder(); + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(icsContent); + return Stream.of(Pair.of(key, builder.build(inputStream))); + } catch (IOException e) { + log("Error while reading input: " + icsContent, e); + return Stream.of(); + } catch (ParserException e) { + log("Error while parsing ICal object: " + icsContent, e); + return Stream.of(); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/2e4005bc/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICalendarParserTest.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICalendarParserTest.java b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICalendarParserTest.java new file mode 100644 index 0000000..c65f69f --- /dev/null +++ b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICalendarParserTest.java @@ -0,0 +1,235 @@ +/**************************************************************** + * 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 org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; +import java.util.Map; + +import javax.mail.MessagingException; + +import org.apache.mailet.Mail; +import org.apache.mailet.base.test.FakeMail; +import org.apache.mailet.base.test.FakeMailetConfig; +import org.apache.mailet.base.test.MimeMessageBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import net.fortuna.ical4j.model.Calendar; + +public class ICalendarParserTest { + private static final String DESTINATION_ATTRIBUTE = "destinationAttribute"; + private static final String SOURCE_ATTRIBUTE = "sourceAttribute"; + + private static final String DESTINATION_CUSTOM_ATTRIBUTE = "ics.dest.attribute"; + private static final String SOURCE_CUSTOM_ATTRIBUTE = "ics.source.attribute"; + + private static final String CONTENT_TRANSFER_ENCODING_VALUE ="8bit"; + + private static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + private static final String CONTENT_TYPE = "Content-Type"; + private static final String TEXT_CALENDAR_CHARSET_UTF_8 = "text/calendar; charset=utf-8"; + + private static final String RIGHT_ICAL_VALUE = "BEGIN:VCALENDAR\n" + + "END:VCALENDAR"; + + private static final String WRONG_ICAL_VALUE = "anyValue"; + + private static MimeMessageBuilder.Header[] CALENDAR_HEADERS = { + new MimeMessageBuilder.Header(CONTENT_TRANSFER_ENCODING, CONTENT_TRANSFER_ENCODING_VALUE), + new MimeMessageBuilder.Header(CONTENT_TYPE, TEXT_CALENDAR_CHARSET_UTF_8) + }; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ICalendarParser mailet = new ICalendarParser(); + + @Test + public void initShouldSetSourceAttributeWhenAbsent() throws Exception { + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .build()); + + assertThat(mailet.getSourceAttributeName()).isEqualTo(ICalendarParser.SOURCE_ATTRIBUTE_PARAMETER_DEFAULT_VALUE); + } + + @Test + public void initShouldSetDestinationAttributeWhenAbsent() throws Exception { + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .build()); + + assertThat(mailet.getDestinationAttributeName()).isEqualTo(ICalendarParser.DESTINATION_ATTRIBUTE_PARAMETER_DEFAULT_VALUE); + } + + @Test + public void initShouldSetSourceAttributeWhenPresent() throws Exception { + String sourceAttribute = "sourceAttribute"; + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(ICalendarParser.SOURCE_ATTRIBUTE_PARAMETER_NAME, sourceAttribute) + .build()); + + assertThat(mailet.getSourceAttributeName()).isEqualTo(sourceAttribute); + } + + @Test + public void initShouldSetDestinationAttributeWhenPresent() throws Exception { + String destinationAttribute = "destinationAttribute"; + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(ICalendarParser.DESTINATION_ATTRIBUTE_PARAMETER_NAME, destinationAttribute) + .build()); + + assertThat(mailet.getDestinationAttributeName()).isEqualTo(destinationAttribute); + } + + @Test + public void initShouldThrowOnEmptySourceAttribute() throws Exception { + expectedException.expect(MessagingException.class); + + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(ICalendarParser.SOURCE_ATTRIBUTE_PARAMETER_NAME, "") + .build()); + } + + @Test + public void initShouldThrowOnEmptyDestinationAttribute() throws Exception { + expectedException.expect(MessagingException.class); + + mailet.init(FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(ICalendarParser.DESTINATION_ATTRIBUTE_PARAMETER_NAME, "") + .build()); + } + + @Test + public void serviceShouldNotSetCalendarDataIntoMailAttributeWhenNoSourceAttribute() throws Exception { + FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(DESTINATION_ATTRIBUTE, DESTINATION_CUSTOM_ATTRIBUTE) + .build(); + mailet.init(mailetConfiguration); + + Mail mail = FakeMail.builder() + .build(); + + mailet.service(mail); + + assertThat(mail.getAttributeNames()).isEmpty(); + } + + @Test + public void serviceShouldSetEmptyCalendarDataIntoMailAttributeWhenEmptyICSAttachments() throws Exception { + FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(SOURCE_ATTRIBUTE, SOURCE_CUSTOM_ATTRIBUTE) + .setProperty(DESTINATION_ATTRIBUTE, DESTINATION_CUSTOM_ATTRIBUTE) + .build(); + mailet.init(mailetConfiguration); + + Mail mail = FakeMail.builder() + .attribute(SOURCE_CUSTOM_ATTRIBUTE, ImmutableMap.of()) + .build(); + + mailet.service(mail); + + assertThat((Map<String, Calendar>)mail.getAttribute(DESTINATION_CUSTOM_ATTRIBUTE)) + .isEmpty(); + } + + @Test + public void serviceShouldNotSetCalendarDataIntoMailAttributeWhenSourceAttributeIsNotAMap() throws Exception { + FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(SOURCE_ATTRIBUTE, SOURCE_CUSTOM_ATTRIBUTE) + .setProperty(DESTINATION_ATTRIBUTE, DESTINATION_CUSTOM_ATTRIBUTE) + .build(); + mailet.init(mailetConfiguration); + + Mail mail = FakeMail.builder() + .attribute(SOURCE_CUSTOM_ATTRIBUTE, "anyValue") + .build(); + + mailet.service(mail); + + assertThat(mail.getAttribute(DESTINATION_CUSTOM_ATTRIBUTE)).isNull(); + } + + @Test + public void serviceShouldReturnRightMapOfCalendarWhenRightAttachments() throws Exception { + FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(SOURCE_ATTRIBUTE, SOURCE_CUSTOM_ATTRIBUTE) + .setProperty(DESTINATION_ATTRIBUTE, DESTINATION_CUSTOM_ATTRIBUTE) + .build(); + mailet.init(mailetConfiguration); + + Map<String, byte[]> attachments = ImmutableMap.<String, byte[]>builder() + .put("key", RIGHT_ICAL_VALUE.getBytes()) + .build(); + + Mail mail = FakeMail.builder() + .attribute(SOURCE_CUSTOM_ATTRIBUTE, (Serializable) attachments) + .build(); + + mailet.service(mail); + + Map<String, Calendar> expectedCalendars = (Map<String, Calendar>)mail.getAttribute(DESTINATION_CUSTOM_ATTRIBUTE); + assertThat(expectedCalendars).hasSize(1); + } + + @Test + public void serviceShouldFilterResultWhenErrorParsing() throws Exception { + FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder() + .mailetName("ICalendarParser") + .setProperty(SOURCE_ATTRIBUTE, SOURCE_CUSTOM_ATTRIBUTE) + .setProperty(DESTINATION_ATTRIBUTE, DESTINATION_CUSTOM_ATTRIBUTE) + .build(); + mailet.init(mailetConfiguration); + + Map<String, byte[]> attachments = ImmutableMap.<String, byte[]>builder() + .put("key1", WRONG_ICAL_VALUE.getBytes()) + .put("key2", RIGHT_ICAL_VALUE.getBytes()) + .build(); + Mail mail = FakeMail.builder() + .attribute(SOURCE_CUSTOM_ATTRIBUTE, (Serializable) attachments) + .build(); + + mailet.service(mail); + + Map<String, Calendar> expectedCalendars = (Map<String, Calendar>)mail.getAttribute(DESTINATION_CUSTOM_ATTRIBUTE); + Map.Entry<String, Calendar> expectedCalendar = Maps.immutableEntry("key2", new Calendar()); + + assertThat(expectedCalendars).hasSize(1) + .containsExactly(expectedCalendar); + } + + @Test + public void getMailetInfoShouldReturn() throws MessagingException { + assertThat(mailet.getMailetInfo()).isEqualTo("Calendar Parser"); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/2e4005bc/mailet/pom.xml ---------------------------------------------------------------------- diff --git a/mailet/pom.xml b/mailet/pom.xml index b5d7cb6..8b443b0 100644 --- a/mailet/pom.xml +++ b/mailet/pom.xml @@ -46,6 +46,7 @@ <commons-collections.version>3.2.1</commons-collections.version> <commons-io.version>2.4</commons-io.version> <commons-lang.version>2.6</commons-lang.version> + <commons-lang3.version>3.3.2</commons-lang3.version> <httpclient-osgi.version>4.5.1</httpclient-osgi.version> <!-- maven-mailetdocs-plugin artifacts --> <maven-artifact.version>3.0-alpha-1</maven-artifact.version> @@ -62,7 +63,7 @@ <jackson-data.version>2.6.3</jackson-data.version> <guavate.version>1.0.0</guavate.version> <ical4j.version>1.0.2</ical4j.version> - <assertj-3.version>3.3.0</assertj-3.version> + <guavate.version>1.0.0</guavate.version> </properties> @@ -165,6 +166,11 @@ <version>${commons-io.version}</version> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>${commons-lang3.version}</version> + </dependency> + <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient-osgi</artifactId> <version>${httpclient-osgi.version}</version> @@ -197,6 +203,11 @@ <version>18.0</version> </dependency> <dependency> + <groupId>com.github.steveash.guavate</groupId> + <artifactId>guavate</artifactId> + <version>${guavate.version}</version> + </dependency> + <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>1.9.5</version> --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org