You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ad...@apache.org on 2017/01/18 13:43:34 UTC

[2/3] james-project git commit: MAILET-148 Create mailet to transform map of ICAL to list of JSONs

MAILET-148 Create mailet to transform map of ICAL to list of JSONs


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

Branch: refs/heads/master
Commit: cb844422302e735d43bff0d9ab51aedf39ce7d97
Parents: 3bdeb52
Author: Benoit Tellier <bt...@linagora.com>
Authored: Mon Jan 16 10:00:40 2017 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Wed Jan 18 17:39:45 2017 +0700

----------------------------------------------------------------------
 mailet/icalendar/pom.xml                        |  33 ++
 .../transport/mailets/ICALToJsonAttribute.java  | 174 ++++++++
 .../james/transport/mailets/model/ICAL.java     | 163 ++++++++
 .../mailets/ICALToJsonAttributeTest.java        | 414 +++++++++++++++++++
 .../james/transport/mailets/model/ICALTest.java | 196 +++++++++
 .../resources/ics/meeting_without_method.ics    |  30 ++
 .../resources/ics/meeting_without_sequence.ics  |  30 ++
 .../test/resources/ics/meeting_without_uid.ics  |  29 ++
 mailet/pom.xml                                  |  17 +
 9 files changed, 1086 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/pom.xml
----------------------------------------------------------------------
diff --git a/mailet/icalendar/pom.xml b/mailet/icalendar/pom.xml
index e64a33b..c66123e 100644
--- a/mailet/icalendar/pom.xml
+++ b/mailet/icalendar/pom.xml
@@ -168,6 +168,21 @@
                     <version>0.5.0</version>
                 </dependency>
                 <dependency>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                    <version>${jackson-data.version}</version>
+                </dependency>
+                <dependency>
+                    <groupId>com.fasterxml.jackson.datatype</groupId>
+                    <artifactId>jackson-datatype-jdk8</artifactId>
+                    <version>${jackson-data.version}</version>
+                </dependency>
+                <dependency>
+                    <groupId>com.github.steveash.guavate</groupId>
+                    <artifactId>guavate</artifactId>
+                    <version>${guavate.version}</version>
+                </dependency>
+                <dependency>
                     <groupId>com.google.guava</groupId>
                     <artifactId>guava</artifactId>
                 </dependency>
@@ -177,6 +192,24 @@
                     <scope>test</scope>
                 </dependency>
                 <dependency>
+                    <groupId>net.javacrumbs.json-unit</groupId>
+                    <artifactId>json-unit</artifactId>
+                    <version>1.5.5</version>
+                    <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>nl.jqno.equalsverifier</groupId>
+                    <artifactId>equalsverifier</artifactId>
+                    <version>1.7.5</version>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
                     <groupId>org.assertj</groupId>
                     <artifactId>assertj-core</artifactId>
                     <version>${assertj-3.version}</version>

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java
new file mode 100644
index 0000000..1e314af
--- /dev/null
+++ b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java
@@ -0,0 +1,174 @@
+/****************************************************************
+ * 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.Serializable;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import javax.mail.MessagingException;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.transport.mailets.model.ICAL;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.base.GenericMailet;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+
+import net.fortuna.ical4j.model.Calendar;
+
+/**
+ * ICALToJsonAttribute takes a map of ICAL4J objects attached as attribute, and output the map of corresponding json bytes as
+ * an other attribute, with unique String keys.
+ *
+ * The JSON contains the following fields :
+ *
+ * <ul>
+ *     <li><b>ical</b> : the raw ical string, in UTF-8</li>
+ *     <li><b>sender</b> : the sender of the mail (compulsory, mail without sender will be discarded)</li>
+ *     <li><b>recipient</b> : the recipient of the mail. If the mail have several recipients, each recipient will have
+ *     its own JSON.</li>
+ *     <li><b>uid</b> : the UID of the ical (optional)</li>
+ *     <li><b>sequence</b> : the sequence of the ical (optional)</li>
+ *     <li><b>dtstamp</b> : the date stamp of the ical (optional)</li>
+ *     <li><b>method</b> : the method of the ical (optional)</li>
+ *     <li><b>recurrence-id</b> : the recurrence-id of the ical (optional)</li>
+ * </ul>
+ *
+ * Example are included in test call ICalToJsonAttributeTest.
+ *
+ *  Configuration example :
+ *
+ * <pre>
+ *     <code>
+ *         &lt;mailet matcher=??? class=ICALToJsonAttribute&gt;
+ *             &lt;sourceAttribute&gt;icalendars&lt;/sourceAttribute&gt;
+ *             &lt;destinationAttribute&gt;icalendarJson&lt;/destinationAttribute&gt;
+ *         &lt;/mailet&gt;
+ *     </code>
+ * </pre>
+ */
+public class ICALToJsonAttribute extends GenericMailet {
+
+    public static final String SOURCE_ATTRIBUTE_NAME = "source";
+    public static final String DESTINATION_ATTRIBUTE_NAME = "destination";
+    public static final String DEFAULT_SOURCE_ATTRIBUTE_NAME = "icalendar";
+    public static final String DEFAULT_DESTINATION_ATTRIBUTE_NAME = "icalendarJson";
+
+    private final ObjectMapper objectMapper;
+    private String sourceAttributeName;
+    private String destinationAttributeName;
+
+    public ICALToJsonAttribute() {
+        this.objectMapper = new ObjectMapper()
+            .registerModule(new Jdk8Module());
+    }
+
+    public String getSourceAttributeName() {
+        return sourceAttributeName;
+    }
+
+    public String getDestinationAttributeName() {
+        return destinationAttributeName;
+    }
+
+    @Override
+    public String getMailetInfo() {
+        return "ICALToJson Mailet";
+    }
+
+    @Override
+    public void init() throws MessagingException {
+        sourceAttributeName = getInitParameter(SOURCE_ATTRIBUTE_NAME, DEFAULT_SOURCE_ATTRIBUTE_NAME);
+        destinationAttributeName = getInitParameter(DESTINATION_ATTRIBUTE_NAME, DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+        if (Strings.isNullOrEmpty(sourceAttributeName)) {
+            throw new MessagingException(SOURCE_ATTRIBUTE_NAME + " configuration parameter can not be null or empty");
+        }
+        if (Strings.isNullOrEmpty(destinationAttributeName)) {
+            throw new MessagingException(DESTINATION_ATTRIBUTE_NAME + " configuration parameter can not be null or empty");
+        }
+    }
+
+    @Override
+    public void service(Mail mail) throws MessagingException {
+        if (mail.getAttribute(sourceAttributeName) == null) {
+            return;
+        }
+        if (mail.getSender() == null) {
+            log("Skipping " + mail.getName() + " because no sender");
+            return;
+        }
+        try {
+            Map<String, Calendar> calendars = getCalendarMap(mail);
+            Map<String, byte[]> jsonsInByteForm = calendars.entrySet()
+                .stream()
+                .flatMap(calendar -> toJson(calendar, mail))
+                .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue));
+            mail.setAttribute(destinationAttributeName, (Serializable) jsonsInByteForm);
+        } catch (ClassCastException e) {
+            log("Received a mail with " + sourceAttributeName + " not being an ICAL object for mail " + mail.getName(), e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Calendar> getCalendarMap(Mail mail) {
+        return (Map<String, Calendar>) mail.getAttribute(sourceAttributeName);
+    }
+
+    private Stream<Pair<String, byte[]>> toJson(Map.Entry<String, Calendar> entry, Mail mail) {
+        return mail.getRecipients()
+            .stream()
+            .flatMap(recipient -> toICAL(entry.getValue(), recipient, mail.getSender()))
+            .flatMap(ical -> toJson(ical, mail.getName()))
+            .map(json -> Pair.of(UUID.randomUUID().toString(), json.getBytes(Charsets.UTF_8)));
+    }
+
+    private Stream<String> toJson(ICAL ical, String mailName) {
+        try {
+            return Stream.of(objectMapper.writeValueAsString(ical));
+        } catch (JsonProcessingException e) {
+            log("Error while serializing Calendar for mail " + mailName, e);
+            return Stream.of();
+        } catch (Exception e) {
+            log("Exception caught while attaching ICAL to the email as JSON for mail " + mailName, e);
+            return Stream.of();
+        }
+    }
+
+    private Stream<ICAL> toICAL(Calendar calendar, MailAddress recipient, MailAddress sender) {
+        try {
+            return Stream.of(ICAL.builder()
+                .from(calendar)
+                .recipient(recipient)
+                .sender(sender)
+                .build());
+        } catch (Exception e) {
+            log("Exception while converting calendar to ICAL", e);
+            return Stream.of();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java
new file mode 100644
index 0000000..b873a93
--- /dev/null
+++ b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java
@@ -0,0 +1,163 @@
+/****************************************************************
+ * 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.model;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.mailet.MailAddress;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.Property;
+import net.fortuna.ical4j.model.component.VEvent;
+
+public class ICAL {
+
+    public static final String DEFAULT_SEQUENCE_VALUE = "0";
+
+    public static class Builder {
+        private String ical;
+        private String sender;
+        private String recipient;
+        private Optional<String> uid = Optional.empty();
+        private Optional<String> sequence = Optional.empty();
+        private Optional<String> dtstamp = Optional.empty();
+        private Optional<String> method = Optional.empty();
+        private Optional<String> recurrenceId = Optional.empty();
+
+        public Builder from(Calendar calendar) {
+            this.ical = calendar.toString();
+            VEvent vevent = (VEvent) calendar.getComponent("VEVENT");
+            this.uid = optionalOf(vevent.getUid());
+            this.method = optionalOf(calendar.getMethod());
+            this.recurrenceId = optionalOf(vevent.getRecurrenceId());
+            this.sequence = optionalOf(vevent.getSequence());
+            this.dtstamp = optionalOf(vevent.getDateStamp());
+            return this;
+        }
+
+        private Optional<String> optionalOf(Property property) {
+            return Optional.ofNullable(property).map(Property::getValue);
+        }
+
+        public Builder sender(MailAddress sender) {
+            this.sender = sender.asString();
+            return this;
+        }
+
+
+        public Builder recipient(MailAddress recipient) {
+            this.recipient = recipient.asString();
+            return this;
+        }
+
+        public ICAL build() {
+            Preconditions.checkNotNull(recipient);
+            Preconditions.checkNotNull(sender);
+            Preconditions.checkNotNull(ical);
+            Preconditions.checkState(uid.isPresent(), "uid is a compulsary property of an ICAL object");
+            Preconditions.checkState(method.isPresent(), "method is a compulsary property of an ICAL object");
+            Preconditions.checkState(dtstamp.isPresent(), "dtstamp is a compulsary property of an ICAL object");
+            return new ICAL(ical, sender, recipient, uid, sequence, dtstamp, method, recurrenceId);
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private final String ical;
+    private final String sender;
+    private final String recipient;
+    private final Optional<String> uid;
+    private final Optional<String> sequence;
+    private final Optional<String> dtstamp;
+    private final Optional<String> method;
+    private final Optional<String> recurrenceId;
+
+    private ICAL(String ical, String sender, String recipient, Optional<String> uid, Optional<String> sequence, Optional<String> dtstamp,
+                 Optional<String> method, Optional<String> recurrenceId) {
+        this.ical = ical;
+        this.sender = sender;
+        this.recipient = recipient;
+        this.uid = uid;
+        this.sequence = sequence;
+        this.dtstamp = dtstamp;
+        this.method = method;
+        this.recurrenceId = recurrenceId;
+    }
+
+    public String getIcal() {
+        return ical;
+    }
+
+    public String getSender() {
+        return sender;
+    }
+
+    public String getRecipient() {
+        return recipient;
+    }
+
+    public Optional<String> getUid() {
+        return uid;
+    }
+
+    public String getSequence() {
+        return sequence.orElse(DEFAULT_SEQUENCE_VALUE);
+    }
+
+    public Optional<String> getDtstamp() {
+        return dtstamp;
+    }
+
+    public Optional<String> getMethod() {
+        return method;
+    }
+
+    @JsonProperty("recurrence-id")
+    public Optional<String> getRecurrenceId() {
+        return recurrenceId;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof ICAL) {
+            ICAL that = (ICAL) o;
+            return Objects.equals(that.ical, this.ical)
+                && Objects.equals(that.sender, this.sender)
+                && Objects.equals(that.recipient, this.recipient)
+                && Objects.equals(that.uid, this.uid)
+                && Objects.equals(that.sequence, this.sequence)
+                && Objects.equals(that.dtstamp, this.dtstamp)
+                && Objects.equals(that.method, this.method)
+                && Objects.equals(that.recurrenceId, this.recurrenceId);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(ical, sender, recipient, uid, sequence, dtstamp, method, recurrenceId);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java
new file mode 100644
index 0000000..f100584
--- /dev/null
+++ b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java
@@ -0,0 +1,414 @@
+/****************************************************************
+ * 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.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.mail.MessagingException;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.base.MailAddressFixture;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableMap;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.model.Calendar;
+
+public class ICALToJsonAttributeTest {
+    public static final MailAddress SENDER = MailAddressFixture.ANY_AT_JAMES;
+    public static final String MEETING_ICS = "\"BEGIN:VCALENDAR\\r\\n" +
+        "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\\r\\n" +
+        "CALSCALE:GREGORIAN\\r\\n" +
+        "X-OBM-TIME:1483703436\\r\\n" +
+        "VERSION:2.0\\r\\n" +
+        "METHOD:REQUEST\\r\\n" +
+        "BEGIN:VEVENT\\r\\n" +
+        "CREATED:20170106T115035Z\\r\\n" +
+        "LAST-MODIFIED:20170106T115036Z\\r\\n" +
+        "DTSTAMP:20170106T115036Z\\r\\n" +
+        "DTSTART:20170111T090000Z\\r\\n" +
+        "DURATION:PT1H30M\\r\\n" +
+        "TRANSP:OPAQUE\\r\\n" +
+        "SEQUENCE:0\\r\\n" +
+        "SUMMARY:Sprint planning #23\\r\\n" +
+        "DESCRIPTION:\\r\\n" +
+        "CLASS:PUBLIC\\r\\n" +
+        "PRIORITY:5\\r\\n" +
+        "ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:ouazana@linagora.com\\r\\n" +
+        "X-OBM-DOMAIN:linagora.com\\r\\n" +
+        "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\\r\\n" +
+        "LOCATION:Hangout\\r\\n" +
+        "CATEGORIES:\\r\\n" +
+        "X-OBM-COLOR:\\r\\n" +
+        "UID:f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Matthieu EXT_BAECHLER;PARTSTAT=NEEDS-ACTION;X-OBM-ID=302:MAILTO:baechler@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Laura ROYET;PARTSTAT=NEEDS-ACTION;X-OBM-ID=723:MAILTO:royet@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Raphael OUAZANA;PARTSTAT=ACCEPTED;X-OBM-ID=128:MAILTO:ouazana@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Luc DUZAN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=715:MAILTO:duzan@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=RESOURCE;CN=Salle de reunion Lyon;PARTSTAT=ACCEPTED;X-OBM-ID=66:MAILTO:noreply@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Antoine DUPRAT;PARTSTAT=NEEDS-ACTION;X-OBM-ID=453:MAILTO:duprat@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=\\\"Beno�t TELLIER\\\";PARTSTAT=NEEDS-ACTION;X-OBM-ID=623:MAILTO:tellier@linagora.com\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Quynh Quynh N NGUYEN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=769:MAILTO:nguyen@linagora.com\\r\\n" +
+        "END:VEVENT\\r\\n" +
+        "END:VCALENDAR\\r\\n" +
+        "\"";
+    public static final String SMALL_ICS = "\"BEGIN:VCALENDAR\\r\\n" +
+        "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\\r\\n" +
+        "CALSCALE:GREGORIAN\\r\\n" +
+        "X-OBM-TIME:1483439571\\r\\n" +
+        "VERSION:2.0\\r\\n" +
+        "METHOD:REQUEST\\r\\n" +
+        "BEGIN:VEVENT\\r\\n" +
+        "CREATED:20170103T103250Z\\r\\n" +
+        "LAST-MODIFIED:20170103T103250Z\\r\\n" +
+        "DTSTAMP:20170103T103250Z\\r\\n" +
+        "DTSTART:20170120T100000Z\\r\\n" +
+        "DURATION:PT30M\\r\\n" +
+        "TRANSP:OPAQUE\\r\\n" +
+        "SEQUENCE:0\\r\\n" +
+        "SUMMARY:Sprint Social #3 Demo\\r\\n" +
+        "DESCRIPTION:\\r\\n" +
+        "CLASS:PUBLIC\\r\\n" +
+        "PRIORITY:5\\r\\n" +
+        "ORGANIZER;X-OBM-ID=468;CN=Attendee 1:MAILTO:attendee1@linagora.com\\r\\n" +
+        "X-OBM-DOMAIN:linagora.com\\r\\n" +
+        "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\\r\\n" +
+        "LOCATION:hangout\\r\\n" +
+        "CATEGORIES:\\r\\n" +
+        "X-OBM-COLOR:\\r\\n" +
+        "UID:f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047feb2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe\\r\\n" +
+        "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Attendee 2;PARTSTAT=NEEDS-ACTION;X-OBM-ID=348:MAILTO:attendee2@linagora.com\\r\\n" +
+        "END:VEVENT\\r\\n" +
+        "END:VCALENDAR\\r\\n" +
+        "\"";
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private ICALToJsonAttribute testee;
+
+    @Before
+    public void setUp() {
+        testee = new ICALToJsonAttribute();
+    }
+
+    @Test
+    public void getMailetInfoShouldReturnExpectedValue() throws Exception {
+        assertThat(testee.getMailetInfo()).isEqualTo("ICALToJson Mailet");
+    }
+
+    @Test
+    public void initShouldSetAttributesWhenAbsent() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        assertThat(testee.getSourceAttributeName()).isEqualTo(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME);
+        assertThat(testee.getDestinationAttributeName()).isEqualTo(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+    }
+
+    @Test
+    public void initShouldThrowOnEmptySourceAttribute() throws Exception {
+        expectedException.expect(MessagingException.class);
+
+        testee.init(FakeMailetConfig.builder()
+            .setProperty(ICALToJsonAttribute.SOURCE_ATTRIBUTE_NAME, "")
+            .build());
+    }
+
+    @Test
+    public void initShouldThrowOnEmptyDestinationAttribute() throws Exception {
+        expectedException.expect(MessagingException.class);
+
+        testee.init(FakeMailetConfig.builder()
+            .setProperty(ICALToJsonAttribute.DESTINATION_ATTRIBUTE_NAME, "")
+            .build());
+    }
+
+    @Test
+    public void initShouldSetAttributesWhenPresent() throws Exception {
+        String destination = "myDestination";
+        String source = "mySource";
+        testee.init(FakeMailetConfig.builder()
+            .setProperty(ICALToJsonAttribute.SOURCE_ATTRIBUTE_NAME, source)
+            .setProperty(ICALToJsonAttribute.DESTINATION_ATTRIBUTE_NAME, destination)
+            .build());
+
+        assertThat(testee.getSourceAttributeName()).isEqualTo(source);
+        assertThat(testee.getDestinationAttributeName()).isEqualTo(destination);
+    }
+
+    @Test
+    public void serviceShouldFilterMailsWithoutICALs() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(MailAddressFixture.OTHER_AT_JAMES)
+            .build();
+        testee.service(mail);
+
+        assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME))
+            .isNull();
+    }
+
+    @Test
+    public void serviceShouldNotFailOnWrongAttributeType() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(MailAddressFixture.OTHER_AT_JAMES)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, "wrong type")
+            .build();
+        testee.service(mail);
+
+        assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME))
+            .isNull();
+    }
+
+    @Test
+    public void serviceShouldNotFailOnWrongAttributeParameter() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.<String, String>builder()
+            .put("key", "value")
+            .build();
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(MailAddressFixture.OTHER_AT_JAMES)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, wrongParametrizedMap)
+            .build();
+        testee.service(mail);
+
+        assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME))
+            .isNull();
+    }
+
+    @Test
+    public void serviceShouldFilterMailsWithoutSender() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .build();
+        Mail mail = FakeMail.builder()
+            .recipient(MailAddressFixture.OTHER_AT_JAMES)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME))
+            .isNull();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void serviceShouldAttachEmptyListWhenNoRecipient() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .build();
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        assertThat((Map) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME))
+            .isEmpty();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void serviceShouldAttachJson() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .build();
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES2;
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(recipient)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        Map<String, byte[]> jsons = (Map<String, byte[]>) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+        assertThat(jsons).hasSize(1);
+        assertThatJson(new String(jsons.values().iterator().next(), Charsets.UTF_8))
+            .isEqualTo("{" +
+                "\"ical\": " + MEETING_ICS +"," +
+                "\"sender\": \"" + SENDER.asString() + "\"," +
+                "\"recipient\": \"" + recipient.asString() + "\"," +
+                "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+                "\"sequence\": \"0\"," +
+                "\"dtstamp\": \"20170106T115036Z\"," +
+                "\"method\": \"REQUEST\"," +
+                "\"recurrence-id\": null" +
+                "}");
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void serviceShouldAttachJsonForSeveralRecipient() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .build();
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipients(MailAddressFixture.OTHER_AT_JAMES, MailAddressFixture.ANY_AT_JAMES2)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        Map<String, byte[]> jsons = (Map<String, byte[]>) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+        assertThat(jsons).hasSize(2);
+        List<String> actual = toSortedValueList(jsons);
+
+        assertThatJson(actual.get(0)).isEqualTo("{" +
+            "\"ical\": " + MEETING_ICS +"," +
+            "\"sender\": \"" + SENDER.asString() + "\"," +
+            "\"recipient\": \"" + MailAddressFixture.ANY_AT_JAMES2.asString() + "\"," +
+            "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+            "\"sequence\": \"0\"," +
+            "\"dtstamp\": \"20170106T115036Z\"," +
+            "\"method\": \"REQUEST\"," +
+            "\"recurrence-id\": null" +
+            "}");
+        assertThatJson(actual.get(1)).isEqualTo("{" +
+            "\"ical\": " + MEETING_ICS +"," +
+            "\"sender\": \"" + SENDER.asString() + "\"," +
+            "\"recipient\": \"" + MailAddressFixture.OTHER_AT_JAMES.asString() + "\"," +
+            "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+            "\"sequence\": \"0\"," +
+            "\"dtstamp\": \"20170106T115036Z\"," +
+            "\"method\": \"REQUEST\"," +
+            "\"recurrence-id\": null" +
+            "}");
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void serviceShouldAttachJsonForSeveralICALs() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        Calendar calendar2 = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_2.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .put("key2", calendar2)
+            .build();
+        MailAddress recipient = MailAddressFixture.OTHER_AT_JAMES;
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(recipient)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        Map<String, byte[]> jsons = (Map<String, byte[]>) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+        assertThat(jsons).hasSize(2);
+        List<String> actual = toSortedValueList(jsons);
+
+        assertThatJson(actual.get(0)).isEqualTo("{" +
+            "\"ical\": " + SMALL_ICS +"," +
+            "\"sender\": \"" + SENDER.asString() + "\"," +
+            "\"recipient\": \"" + recipient.asString() + "\"," +
+            "\"uid\": \"f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047feb2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe\"," +
+            "\"sequence\": \"0\"," +
+            "\"dtstamp\": \"20170103T103250Z\"," +
+            "\"method\": \"REQUEST\"," +
+            "\"recurrence-id\": null" +
+            "}");
+        assertThatJson(actual.get(1)).isEqualTo("{" +
+            "\"ical\": " + MEETING_ICS +"," +
+            "\"sender\": \"" + SENDER.asString() + "\"," +
+            "\"recipient\": \"" + recipient.asString() + "\"," +
+            "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+            "\"sequence\": \"0\"," +
+            "\"dtstamp\": \"20170106T115036Z\"," +
+            "\"method\": \"REQUEST\"," +
+            "\"recurrence-id\": null" +
+            "}");
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void serviceShouldFilterInvalidICS() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+        Calendar calendar2 = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics"));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder()
+            .put("key", calendar)
+            .put("key2", calendar2)
+            .build();
+        MailAddress recipient = MailAddressFixture.OTHER_AT_JAMES;
+        Mail mail = FakeMail.builder()
+            .sender(SENDER)
+            .recipient(recipient)
+            .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals)
+            .build();
+        testee.service(mail);
+
+        Map<String, byte[]> jsons = (Map<String, byte[]>) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+        assertThat(jsons).hasSize(1);
+        List<String> actual = toSortedValueList(jsons);
+
+        assertThatJson(actual.get(0)).isEqualTo("{" +
+            "\"ical\": " + MEETING_ICS + "," +
+            "\"sender\": \"" + SENDER.asString() + "\"," +
+            "\"recipient\": \"" + recipient.asString() + "\"," +
+            "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+            "\"sequence\": \"0\"," +
+            "\"dtstamp\": \"20170106T115036Z\"," +
+            "\"method\": \"REQUEST\"," +
+            "\"recurrence-id\": null" +
+            "}");
+    }
+
+    private List<String> toSortedValueList(Map<String, byte[]> jsons) {
+        return jsons.values()
+                .stream()
+                .map(bytes -> new String(bytes, Charsets.UTF_8))
+                .sorted()
+                .collect(Collectors.toList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java
new file mode 100644
index 0000000..f8ab4ab
--- /dev/null
+++ b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java
@@ -0,0 +1,196 @@
+/****************************************************************
+ * 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.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.base.MailAddressFixture;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.model.Calendar;
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class ICALTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Test
+    public void buildShouldFailWhenNoCalendar() throws Exception {
+        expectedException.expect(NullPointerException.class);
+
+        ICAL.builder()
+            .recipient(MailAddressFixture.ANY_AT_JAMES)
+            .sender(MailAddressFixture.OTHER_AT_JAMES)
+            .build();
+    }
+
+    @Test
+    public void buildShouldFailWhenNoSender() throws Exception {
+        expectedException.expect(NullPointerException.class);
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+
+        ICAL.builder()
+            .recipient(MailAddressFixture.ANY_AT_JAMES)
+            .from(calendar)
+            .build();
+    }
+
+    @Test
+    public void buildShouldFailWhenNoRecipient() throws Exception {
+        expectedException.expect(NullPointerException.class);
+
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+
+        ICAL.builder()
+            .sender(MailAddressFixture.OTHER_AT_JAMES)
+            .from(calendar)
+            .build();
+    }
+
+
+    @Test
+    public void buildShouldWork() throws Exception {
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics"));
+
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
+        MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
+        ICAL ical = ICAL.builder()
+            .recipient(recipient)
+            .sender(sender)
+            .from(calendar)
+            .build();
+
+        assertThat(ical.getRecipient()).isEqualTo(recipient.asString());
+        assertThat(ical.getSender()).isEqualTo(sender.asString());
+        assertThat(ical.getUid())
+            .contains("f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7" +
+                "c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc");
+        assertThat(ical.getMethod()).contains("REQUEST");
+        assertThat(ical.getRecurrenceId()).isEmpty();
+        assertThat(ical.getDtstamp()).contains("20170106T115036Z");
+        assertThat(ical.getSequence()).isEqualTo("0");
+        assertThat(ical.getIcal()).isEqualTo("BEGIN:VCALENDAR\r\n" +
+            "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\r\n" +
+            "CALSCALE:GREGORIAN\r\n" +
+            "X-OBM-TIME:1483703436\r\n" +
+            "VERSION:2.0\r\n" +
+            "METHOD:REQUEST\r\n" +
+            "BEGIN:VEVENT\r\n" +
+            "CREATED:20170106T115035Z\r\n" +
+            "LAST-MODIFIED:20170106T115036Z\r\n" +
+            "DTSTAMP:20170106T115036Z\r\n" +
+            "DTSTART:20170111T090000Z\r\n" +
+            "DURATION:PT1H30M\r\n" +
+            "TRANSP:OPAQUE\r\n" +
+            "SEQUENCE:0\r\n" +
+            "SUMMARY:Sprint planning #23\r\n" +
+            "DESCRIPTION:\r\n" +
+            "CLASS:PUBLIC\r\n" +
+            "PRIORITY:5\r\n" +
+            "ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:ouazana@linagora.com\r\n" +
+            "X-OBM-DOMAIN:linagora.com\r\n" +
+            "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\r\n" +
+            "LOCATION:Hangout\r\n" +
+            "CATEGORIES:\r\n" +
+            "X-OBM-COLOR:\r\n" +
+            "UID:f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Matthieu EXT_BAECHLER;PARTSTAT=NEEDS-ACTION;X-OBM-ID=302:MAILTO:baechler@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Laura ROYET;PARTSTAT=NEEDS-ACTION;X-OBM-ID=723:MAILTO:royet@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Raphael OUAZANA;PARTSTAT=ACCEPTED;X-OBM-ID=128:MAILTO:ouazana@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Luc DUZAN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=715:MAILTO:duzan@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=RESOURCE;CN=Salle de reunion Lyon;PARTSTAT=ACCEPTED;X-OBM-ID=66:MAILTO:noreply@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Antoine DUPRAT;PARTSTAT=NEEDS-ACTION;X-OBM-ID=453:MAILTO:duprat@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=\"Beno�t TELLIER\";PARTSTAT=NEEDS-ACTION;X-OBM-ID=623:MAILTO:tellier@linagora.com\r\n" +
+            "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Quynh Quynh N NGUYEN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=769:MAILTO:nguyen@linagora.com\r\n" +
+            "END:VEVENT\r\n" +
+            "END:VCALENDAR\r\n");
+    }
+
+    @Test
+    public void equalsAndHashCodeShouldBeWellImplemented() {
+        EqualsVerifier.forClass(ICAL.class).verify();
+    }
+
+    @Test
+    public void buildShouldThrowOnCalendarWithoutDtstamp() throws Exception {
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_dtstamp.ics"));
+
+        expectedException.expect(IllegalStateException.class);
+
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
+        MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
+        ICAL.builder()
+            .recipient(recipient)
+            .sender(sender)
+            .from(calendar)
+            .build();
+    }
+
+    @Test
+    public void buildShouldThrowOnCalendarWithoutUid() throws Exception {
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics"));
+
+        expectedException.expect(IllegalStateException.class);
+
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
+        MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
+        ICAL.builder()
+            .recipient(recipient)
+            .sender(sender)
+            .from(calendar)
+            .build();
+    }
+
+    @Test
+    public void buildShouldThrowOnCalendarWithoutMethod() throws Exception {
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_method.ics"));
+
+        expectedException.expect(IllegalStateException.class);
+
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
+        MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
+        ICAL.builder()
+            .recipient(recipient)
+            .sender(sender)
+            .from(calendar)
+            .build();
+    }
+
+    @Test
+    public void buildShouldSetDefaultValueWhenCalendarWithoutSequence() throws Exception {
+        Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_sequence.ics"));
+
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
+        MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
+        ICAL ical = ICAL.builder()
+            .recipient(recipient)
+            .sender(sender)
+            .from(calendar)
+            .build();
+
+        assertThat(ical.getSequence()).isEqualTo(ICAL.DEFAULT_SEQUENCE_VALUE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/test/resources/ics/meeting_without_method.ics
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/test/resources/ics/meeting_without_method.ics b/mailet/icalendar/src/test/resources/ics/meeting_without_method.ics
new file mode 100644
index 0000000..2d628f6
--- /dev/null
+++ b/mailet/icalendar/src/test/resources/ics/meeting_without_method.ics
@@ -0,0 +1,30 @@
+BEGIN:VCALENDAR
+PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR
+CALSCALE:GREGORIAN
+X-OBM-TIME:1483439571
+VERSION:2.0
+BEGIN:VEVENT
+CREATED:20170103T103250Z
+LAST-MODIFIED:20170103T103250Z
+DTSTAMP:20170103T103250Z
+DTSTART:20170120T100000Z
+DURATION:PT30M
+TRANSP:OPAQUE
+SEQUENCE:0
+SUMMARY:Sprint Social #3 Demo
+DESCRIPTION:
+CLASS:PUBLIC
+PRIORITY:5
+ORGANIZER;X-OBM-ID=468;CN=Attendee 1:MAILTO:attendee1@linagora.
+ com
+X-OBM-DOMAIN:linagora.com
+X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922
+LOCATION:hangout
+CATEGORIES:
+X-OBM-COLOR:
+UID:f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047fe
+ b2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe
+ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Attendee 2;PARTSTAT=NEEDS-ACTI
+ ON;X-OBM-ID=348:MAILTO:attendee2@linagora.com
+END:VEVENT
+END:VCALENDAR

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/test/resources/ics/meeting_without_sequence.ics
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/test/resources/ics/meeting_without_sequence.ics b/mailet/icalendar/src/test/resources/ics/meeting_without_sequence.ics
new file mode 100644
index 0000000..ad57338
--- /dev/null
+++ b/mailet/icalendar/src/test/resources/ics/meeting_without_sequence.ics
@@ -0,0 +1,30 @@
+BEGIN:VCALENDAR
+PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR
+CALSCALE:GREGORIAN
+X-OBM-TIME:1483439571
+VERSION:2.0
+METHOD:REQUEST
+BEGIN:VEVENT
+CREATED:20170103T103250Z
+LAST-MODIFIED:20170103T103250Z
+DTSTAMP:20170103T103250Z
+DTSTART:20170120T100000Z
+DURATION:PT30M
+TRANSP:OPAQUE
+SUMMARY:Sprint Social #3 Demo
+DESCRIPTION:
+CLASS:PUBLIC
+PRIORITY:5
+ORGANIZER;X-OBM-ID=468;CN=Attendee 1:MAILTO:attendee1@linagora.
+ com
+X-OBM-DOMAIN:linagora.com
+X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922
+LOCATION:hangout
+CATEGORIES:
+X-OBM-COLOR:
+UID:f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047fe
+ b2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe
+ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Attendee 2;PARTSTAT=NEEDS-ACTI
+ ON;X-OBM-ID=348:MAILTO:attendee2@linagora.com
+END:VEVENT
+END:VCALENDAR

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/icalendar/src/test/resources/ics/meeting_without_uid.ics
----------------------------------------------------------------------
diff --git a/mailet/icalendar/src/test/resources/ics/meeting_without_uid.ics b/mailet/icalendar/src/test/resources/ics/meeting_without_uid.ics
new file mode 100644
index 0000000..f7e0c2d
--- /dev/null
+++ b/mailet/icalendar/src/test/resources/ics/meeting_without_uid.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR
+CALSCALE:GREGORIAN
+X-OBM-TIME:1483439571
+VERSION:2.0
+METHOD:REQUEST
+BEGIN:VEVENT
+CREATED:20170103T103250Z
+LAST-MODIFIED:20170103T103250Z
+DTSTAMP:20170103T103250Z
+DTSTART:20170120T100000Z
+DURATION:PT30M
+TRANSP:OPAQUE
+SEQUENCE:0
+SUMMARY:Sprint Social #3 Demo
+DESCRIPTION:
+CLASS:PUBLIC
+PRIORITY:5
+ORGANIZER;X-OBM-ID=468;CN=Attendee 1:MAILTO:attendee1@linagora.
+ com
+X-OBM-DOMAIN:linagora.com
+X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922
+LOCATION:hangout
+CATEGORIES:
+X-OBM-COLOR:
+ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Attendee 2;PARTSTAT=NEEDS-ACTI
+ ON;X-OBM-ID=348:MAILTO:attendee2@linagora.com
+END:VEVENT
+END:VCALENDAR

http://git-wip-us.apache.org/repos/asf/james-project/blob/cb844422/mailet/pom.xml
----------------------------------------------------------------------
diff --git a/mailet/pom.xml b/mailet/pom.xml
index 1c68cad..fa203b2 100644
--- a/mailet/pom.xml
+++ b/mailet/pom.xml
@@ -59,6 +59,8 @@
         <assertj-3.version>3.3.0</assertj-3.version>
         <assertj-1-guava.version>1.3.1</assertj-1-guava.version>
         <slf4j.version>1.7.7</slf4j.version>
+        <jackson-data.version>2.6.3</jackson-data.version>
+        <guavate.version>1.0.0</guavate.version>
     </properties>
 
 
@@ -197,6 +199,21 @@
                 <artifactId>slf4j-api</artifactId>
                 <version>${slf4j.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>${jackson-data.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-jdk8</artifactId>
+                <version>${jackson-data.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.steveash.guavate</groupId>
+                <artifactId>guavate</artifactId>
+                <version>${guavate.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 


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