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 bt...@apache.org on 2020/06/19 04:12:16 UTC

[james-project] 12/12: JAMES-3213 Source ReplyTo in ICALToJsonAttribute

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 60d08c54a2347b16efe8348acd6ac7e285ee5b25
Author: Gautier DI FOLCO <gd...@linagora.com>
AuthorDate: Thu Jun 11 16:01:34 2020 +0200

    JAMES-3213 Source ReplyTo in ICALToJsonAttribute
---
 .../transport/mailets/ICALToJsonAttribute.java     |  88 ++++++++--
 .../model/{ICAL.java => ICALAttributeDTO.java}     | 103 +++++------
 .../transport/mailets/ICALToJsonAttributeTest.java | 188 ++++++++++++++++-----
 .../{ICALTest.java => ICALAttributeDTOTest.java}   |  83 +++------
 4 files changed, 290 insertions(+), 172 deletions(-)

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
index d3eafad..ed0ea90 100644
--- 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
@@ -27,12 +27,17 @@ import java.util.stream.Stream;
 
 import javax.mail.Address;
 import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.MailAddress;
-import org.apache.james.transport.mailets.model.ICAL;
+import org.apache.james.core.MaybeSender;
+import org.apache.james.mime4j.dom.address.Group;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.field.address.LenientAddressParser;
+import org.apache.james.transport.mailets.model.ICALAttributeDTO;
 import org.apache.james.util.StreamUtils;
 import org.apache.mailet.Attribute;
 import org.apache.mailet.AttributeName;
@@ -97,12 +102,10 @@ public class ICALToJsonAttribute extends GenericMailet {
     public static final String DEFAULT_SOURCE_ATTRIBUTE_NAME = "icalendar";
     public static final String DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME = "attachments";
     public static final String DEFAULT_DESTINATION_ATTRIBUTE_NAME = "icalendarJson";
-    public static final AttributeName SOURCE = AttributeName.of(SOURCE_ATTRIBUTE_NAME);
-    public static final AttributeName RAW_SOURCE = AttributeName.of(RAW_SOURCE_ATTRIBUTE_NAME);
-    public static final AttributeName DESTINATION = AttributeName.of(DESTINATION_ATTRIBUTE_NAME);
     public static final AttributeName DEFAULT_SOURCE = AttributeName.of(DEFAULT_SOURCE_ATTRIBUTE_NAME);
     public static final AttributeName DEFAULT_RAW_SOURCE = AttributeName.of(DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME);
     public static final AttributeName DEFAULT_DESTINATION = AttributeName.of(DEFAULT_DESTINATION_ATTRIBUTE_NAME);
+    public static final String REPLY_TO_HEADER_NAME = "replyTo";
 
     static {
         ICal4JConfigurator.configure();
@@ -170,27 +173,72 @@ public class ICALToJsonAttribute extends GenericMailet {
     }
 
     private void setAttribute(Mail mail, Map<String, Calendar> calendars, Map<String, byte[]> rawCalendars) throws MessagingException {
-        Optional<String> sender = retrieveSender(mail);
+        Optional<MailAddress> sender = retrieveSender(mail);
         if (!sender.isPresent()) {
             LOGGER.info("Skipping {} because no sender and no from", mail.getName());
             return;
         }
+
+        MailAddress transportSender = sender.get();
+        MailAddress replyTo = fetchReplyTo(mail).orElse(transportSender);
+
         Map<String, byte[]> jsonsInByteForm = calendars.entrySet()
             .stream()
-            .flatMap(calendar -> toJson(calendar, rawCalendars, mail, sender.get()))
+            .flatMap(calendar -> toJson(calendar, rawCalendars, mail, transportSender, replyTo))
             .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue));
         mail.setAttribute(new Attribute(destinationAttributeName, AttributeValue.ofAny(jsonsInByteForm)));
     }
 
-    private Stream<Pair<String, byte[]>> toJson(Map.Entry<String, Calendar> entry, Map<String, byte[]> rawCalendars, Mail mail, String sender) {
+    private Optional<MailAddress> fetchReplyTo(Mail mail) throws MessagingException {
+        return Optional.ofNullable(mail.getMessage())
+            .flatMap(Throwing.<MimeMessage, Optional<String[]>>function(mimeMessage ->
+                    Optional.ofNullable(mimeMessage.getHeader(REPLY_TO_HEADER_NAME))
+                ).sneakyThrow())
+            .filter(headers -> headers.length > 0)
+            .map(headers -> headers[0])
+            .flatMap(this::retrieveReplyTo);
+    }
+
+    private Optional<MailAddress> retrieveReplyTo(String headerValue) {
+        return LenientAddressParser.DEFAULT
+            .parseAddressList(headerValue)
+            .stream()
+            .flatMap(this::convertAddressToMailboxStream)
+            .flatMap(this::convertMailboxToMailAddress)
+            .findFirst();
+
+    }
+
+    private Stream<MailAddress> convertMailboxToMailAddress(Mailbox mailbox) {
+        try {
+            return Stream.of(new MailAddress(mailbox.getAddress()));
+        } catch (AddressException e) {
+            return Stream.empty();
+        }
+    }
+
+    private Stream<Mailbox> convertAddressToMailboxStream(org.apache.james.mime4j.dom.address.Address address) {
+        if (address instanceof Mailbox) {
+            return Stream.of((Mailbox) address);
+        } else if (address instanceof Group) {
+            return ((Group) address).getMailboxes().stream();
+        }
+        return Stream.empty();
+    }
+
+    private Stream<Pair<String, byte[]>> toJson(Map.Entry<String, Calendar> entry,
+                                                Map<String, byte[]> rawCalendars,
+                                                Mail mail,
+                                                MailAddress sender,
+                                                MailAddress replyTo) {
         return mail.getRecipients()
             .stream()
-            .flatMap(recipient -> toICAL(entry, rawCalendars, recipient, sender))
+            .flatMap(recipient -> toICAL(entry, rawCalendars, recipient, sender, replyTo))
             .flatMap(ical -> toJson(ical, mail.getName()))
             .map(json -> Pair.of(UUID.randomUUID().toString(), json.getBytes(StandardCharsets.UTF_8)));
     }
 
-    private Stream<String> toJson(ICAL ical, String mailName) {
+    private Stream<String> toJson(ICALAttributeDTO ical, String mailName) {
         try {
             return Stream.of(objectMapper.writeValueAsString(ical));
         } catch (JsonProcessingException e) {
@@ -202,7 +250,11 @@ public class ICALToJsonAttribute extends GenericMailet {
         }
     }
 
-    private Stream<ICAL> toICAL(Map.Entry<String, Calendar> entry, Map<String, byte[]> rawCalendars, MailAddress recipient, String sender) {
+    private Stream<ICALAttributeDTO> toICAL(Map.Entry<String, Calendar> entry,
+                                            Map<String, byte[]> rawCalendars,
+                                            MailAddress recipient,
+                                            MailAddress sender,
+                                            MailAddress replyTo) {
         Calendar calendar = entry.getValue();
         byte[] rawICal = rawCalendars.get(entry.getKey());
         if (rawICal == null) {
@@ -210,27 +262,27 @@ public class ICALToJsonAttribute extends GenericMailet {
             return Stream.of();
         }
         try {
-            return Stream.of(ICAL.builder()
+            return Stream.of(ICALAttributeDTO.builder()
                 .from(calendar, rawICal)
-                .recipient(recipient)
                 .sender(sender)
-                .build());
+                .recipient(recipient)
+                .replyTo(replyTo));
         } catch (Exception e) {
             LOGGER.error("Exception while converting calendar to ICAL", e);
             return Stream.of();
         }
     }
 
-    private Optional<String> retrieveSender(Mail mail) throws MessagingException {
-        Optional<String> fromMime = StreamUtils.ofOptional(
+    private Optional<MailAddress> retrieveSender(Mail mail) throws MessagingException {
+        Optional<MailAddress> fromMime = StreamUtils.ofOptional(
             Optional.ofNullable(mail.getMessage())
                 .map(Throwing.function(MimeMessage::getFrom).orReturn(new Address[]{})))
             .map(address -> (InternetAddress) address)
             .map(InternetAddress::getAddress)
+            .map(MaybeSender::getMailSender)
+            .flatMap(MaybeSender::asStream)
             .findFirst();
-        Optional<String> fromEnvelope = mail.getMaybeSender().asOptional()
-            .map(MailAddress::asString);
 
-        return fromMime.or(() -> fromEnvelope);
+        return fromMime.or(() -> mail.getMaybeSender().asOptional());
     }
 }
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/ICALAttributeDTO.java
similarity index 63%
rename from mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java
rename to mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICALAttributeDTO.java
index 7a09d65..e594e4e 100644
--- 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/ICALAttributeDTO.java
@@ -32,54 +32,52 @@ import net.fortuna.ical4j.model.Calendar;
 import net.fortuna.ical4j.model.Property;
 import net.fortuna.ical4j.model.component.VEvent;
 
-public class ICAL {
+public class ICALAttributeDTO {
 
     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, byte[] originalEvent) {
-            this.ical = new String(originalEvent, StandardCharsets.UTF_8);
+        public RequiresSender from(Calendar calendar, byte[] originalEvent) {
+            String ical = new String(originalEvent, StandardCharsets.UTF_8);
             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;
+            Optional<String> uid = optionalOf(vevent.getUid());
+            Optional<String> method = optionalOf(calendar.getMethod());
+            Optional<String> recurrenceId = optionalOf(vevent.getRecurrenceId());
+            Optional<String> sequence = optionalOf(vevent.getSequence());
+            Optional<String> dtstamp = optionalOf(vevent.getDateStamp());
+
+            Preconditions.checkNotNull(ical);
+            Preconditions.checkState(uid.isPresent(), "uid is a compulsory property of an ICAL object");
+            Preconditions.checkState(method.isPresent(), "method is a compulsory property of an ICAL object");
+            Preconditions.checkState(dtstamp.isPresent(), "dtstamp is a compulsory property of an ICAL object");
+
+            return sender -> recipient -> replyTo ->
+                    new ICALAttributeDTO(
+                            ical,
+                            uid.get(), sender.asString(),
+                            recipient.asString(),
+                            replyTo.asString(),
+                            dtstamp.get(), method.get(), sequence,
+                            recurrenceId);
         }
 
         private Optional<String> optionalOf(Property property) {
             return Optional.ofNullable(property).map(Property::getValue);
         }
 
-        public Builder sender(String sender) {
-            this.sender = sender;
-            return this;
+        @FunctionalInterface
+        public interface RequiresSender {
+            RequiresRecipient sender(MailAddress sender);
         }
 
-
-        public Builder recipient(MailAddress recipient) {
-            this.recipient = recipient.asString();
-            return this;
+        @FunctionalInterface
+        public interface RequiresRecipient {
+            RequiresReplyTo recipient(MailAddress recipient);
         }
 
-        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);
+        @FunctionalInterface
+        public interface RequiresReplyTo {
+            ICALAttributeDTO replyTo(MailAddress replyTo);
         }
     }
 
@@ -90,21 +88,23 @@ public class ICAL {
     private final String ical;
     private final String sender;
     private final String recipient;
-    private final Optional<String> uid;
+    private final String replyTo;
+    private final String uid;
+    private final String dtstamp;
+    private final String method;
     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) {
+    private ICALAttributeDTO(String ical, String uid, String sender, String recipient, String replyTo, String dtstamp,
+                             String method, Optional<String> sequence, Optional<String> recurrenceId) {
         this.ical = ical;
         this.sender = sender;
         this.recipient = recipient;
+        this.replyTo = replyTo;
         this.uid = uid;
-        this.sequence = sequence;
         this.dtstamp = dtstamp;
         this.method = method;
+        this.sequence = sequence;
         this.recurrenceId = recurrenceId;
     }
 
@@ -120,22 +120,26 @@ public class ICAL {
         return recipient;
     }
 
-    public Optional<String> getUid() {
-        return uid;
+    public String getReplyTo() {
+        return replyTo;
     }
 
-    public String getSequence() {
-        return sequence.orElse(DEFAULT_SEQUENCE_VALUE);
+    public String getUid() {
+        return uid;
     }
 
-    public Optional<String> getDtstamp() {
+    public String getDtstamp() {
         return dtstamp;
     }
 
-    public Optional<String> getMethod() {
+    public String getMethod() {
         return method;
     }
 
+    public String getSequence() {
+        return sequence.orElse(DEFAULT_SEQUENCE_VALUE);
+    }
+
     @JsonProperty("recurrence-id")
     public Optional<String> getRecurrenceId() {
         return recurrenceId;
@@ -143,8 +147,8 @@ public class ICAL {
 
     @Override
     public final boolean equals(Object o) {
-        if (o instanceof ICAL) {
-            ICAL that = (ICAL) o;
+        if (o instanceof ICALAttributeDTO) {
+            ICALAttributeDTO that = (ICALAttributeDTO) o;
             return Objects.equals(that.ical, this.ical)
                 && Objects.equals(that.sender, this.sender)
                 && Objects.equals(that.recipient, this.recipient)
@@ -152,13 +156,14 @@ public class ICAL {
                 && Objects.equals(that.sequence, this.sequence)
                 && Objects.equals(that.dtstamp, this.dtstamp)
                 && Objects.equals(that.method, this.method)
-                && Objects.equals(that.recurrenceId, this.recurrenceId);
+                && Objects.equals(that.recurrenceId, this.recurrenceId)
+                && Objects.equals(that.replyTo, this.replyTo);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(ical, sender, recipient, uid, sequence, dtstamp, method, recurrenceId);
+        return Objects.hash(ical, sender, recipient, uid, sequence, dtstamp, method, recurrenceId, replyTo);
     }
 }
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
index 40111b7..35666d2 100644
--- 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
@@ -21,12 +21,14 @@ package org.apache.james.transport.mailets;
 
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.mail.MessagingException;
 
@@ -41,10 +43,11 @@ import org.apache.mailet.Mail;
 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 org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import com.fasterxml.jackson.core.util.BufferRecyclers;
 import com.google.common.collect.ImmutableMap;
@@ -56,23 +59,20 @@ public class ICALToJsonAttributeTest {
     @SuppressWarnings("unchecked")
     private static final Class<Map<String, byte[]>> MAP_STRING_BYTES_CLASS = (Class<Map<String, byte[]>>) (Object) Map.class;
 
-    @Rule
-    public ExpectedException expectedException = ExpectedException.none();
-
     private ICALToJsonAttribute testee;
 
-    @Before
-    public void setUp() {
+    @BeforeEach
+    void setUp() {
         testee = new ICALToJsonAttribute();
     }
 
     @Test
-    public void getMailetInfoShouldReturnExpectedValue() {
+    void getMailetInfoShouldReturnExpectedValue() {
         assertThat(testee.getMailetInfo()).isEqualTo("ICALToJson Mailet");
     }
 
     @Test
-    public void initShouldSetAttributesWhenAbsent() throws Exception {
+    void initShouldSetAttributesWhenAbsent() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         assertThat(testee.getSourceAttributeName()).isEqualTo(ICALToJsonAttribute.DEFAULT_SOURCE);
@@ -80,34 +80,31 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void initShouldThrowOnEmptySourceAttribute() throws Exception {
-        expectedException.expect(MessagingException.class);
-
-        testee.init(FakeMailetConfig.builder()
-            .setProperty(ICALToJsonAttribute.SOURCE_ATTRIBUTE_NAME, "")
-            .build());
+    void initShouldThrowOnEmptySourceAttribute() {
+        assertThatThrownBy(() -> testee.init(FakeMailetConfig.builder()
+                .setProperty(ICALToJsonAttribute.SOURCE_ATTRIBUTE_NAME, "")
+                .build()))
+            .isInstanceOf(MessagingException.class);
     }
 
     @Test
-    public void initShouldThrowOnEmptyRawSourceAttribute() throws Exception {
-        expectedException.expect(MessagingException.class);
-
-        testee.init(FakeMailetConfig.builder()
-            .setProperty(ICALToJsonAttribute.RAW_SOURCE_ATTRIBUTE_NAME, "")
-            .build());
+    void initShouldThrowOnEmptyRawSourceAttribute() {
+        assertThatThrownBy(() -> testee.init(FakeMailetConfig.builder()
+                .setProperty(ICALToJsonAttribute.RAW_SOURCE_ATTRIBUTE_NAME, "")
+                .build()))
+            .isInstanceOf(MessagingException.class);
     }
 
     @Test
-    public void initShouldThrowOnEmptyDestinationAttribute() throws Exception {
-        expectedException.expect(MessagingException.class);
-
-        testee.init(FakeMailetConfig.builder()
-            .setProperty(ICALToJsonAttribute.DESTINATION_ATTRIBUTE_NAME, "")
-            .build());
+    void initShouldThrowOnEmptyDestinationAttribute() {
+        assertThatThrownBy(() -> testee.init(FakeMailetConfig.builder()
+                .setProperty(ICALToJsonAttribute.DESTINATION_ATTRIBUTE_NAME, "")
+                .build()))
+            .isInstanceOf(MessagingException.class);
     }
 
     @Test
-    public void initShouldSetAttributesWhenPresent() throws Exception {
+    void initShouldSetAttributesWhenPresent() throws Exception {
         String destination = "myDestination";
         String source = "mySource";
         String raw = "myRaw";
@@ -123,7 +120,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldFilterMailsWithoutICALs() throws Exception {
+    void serviceShouldFilterMailsWithoutICALs() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         Mail mail = FakeMail.builder()
@@ -138,7 +135,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldNotFailOnWrongAttributeType() throws Exception {
+    void serviceShouldNotFailOnWrongAttributeType() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         Mail mail = FakeMail.builder()
@@ -154,7 +151,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldNotFailOnWrongRawAttributeType() throws Exception {
+    void serviceShouldNotFailOnWrongRawAttributeType() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         Mail mail = FakeMail.builder()
@@ -170,7 +167,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldNotFailOnWrongAttributeParameter() throws Exception {
+    void serviceShouldNotFailOnWrongAttributeParameter() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.of("key", "value");
@@ -187,7 +184,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldNotFailOnWrongRawAttributeParameter() throws Exception {
+    void serviceShouldNotFailOnWrongRawAttributeParameter() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.of("key", "value");
@@ -204,7 +201,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldFilterMailsWithoutSender() throws Exception {
+    void serviceShouldFilterMailsWithoutSender() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -224,7 +221,7 @@ public class ICALToJsonAttributeTest {
 
     @SuppressWarnings("unchecked")
     @Test
-    public void serviceShouldAttachEmptyListWhenNoRecipient() throws Exception {
+    void serviceShouldAttachEmptyListWhenNoRecipient() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -245,7 +242,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldAttachJson() throws Exception {
+    void serviceShouldAttachJson() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -270,6 +267,7 @@ public class ICALToJsonAttributeTest {
                     .isEqualTo("{" +
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + recipient.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
@@ -284,8 +282,99 @@ public class ICALToJsonAttributeTest {
         return new String(BufferRecyclers.getJsonStringEncoder().quoteAsUTF8(new String(ics, StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
     }
 
+    @ParameterizedTest
+    @MethodSource("validReplyToHeaders")
+    void serviceShouldAttachJsonWithTheReplyToAttributeValueWhenPresent(String replyToHeader) throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
+        Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar);
+        ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics);
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES2;
+        MailAddress replyTo = MailAddressFixture.OTHER_AT_JAMES;
+        Mail mail = FakeMail.builder()
+            .name("mail")
+            .sender(SENDER)
+            .recipient(recipient)
+            .attribute(new Attribute(ICALToJsonAttribute.DEFAULT_SOURCE, AttributeValue.ofAny(icals)))
+            .attribute(new Attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE, AttributeValue.ofAny(rawIcals)))
+            .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+                .addHeader(ICALToJsonAttribute.REPLY_TO_HEADER_NAME, replyToHeader))
+            .build();
+        testee.service(mail);
+
+        assertThat(AttributeUtils.getValueAndCastFromMail(mail, ICALToJsonAttribute.DEFAULT_DESTINATION, MAP_STRING_BYTES_CLASS))
+            .isPresent()
+            .hasValueSatisfying(jsons -> {
+                assertThat(jsons).hasSize(1);
+                assertThatJson(new String(jsons.values().iterator().next(), StandardCharsets.UTF_8))
+                    .isEqualTo("{" +
+                        "\"ical\": \"" + toJsonValue(ics) + "\"," +
+                        "\"sender\": \"" + SENDER.asString() + "\"," +
+                        "\"replyTo\": \"" + replyTo.asString() + "\"," +
+                        "\"recipient\": \"" + recipient.asString() + "\"," +
+                        "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+                        "\"sequence\": \"0\"," +
+                        "\"dtstamp\": \"20170106T115036Z\"," +
+                        "\"method\": \"REQUEST\"," +
+                        "\"recurrence-id\": null" +
+                        "}");
+            });
+    }
+
+    private static Stream<Arguments> validReplyToHeaders() {
+        String address = MailAddressFixture.OTHER_AT_JAMES.asString();
+        return Stream.of(
+                address,
+                "<" + address + ">",
+                "\"Bob\" <" + address + ">",
+                "\"Bob\"\n      <" + address + ">",
+                " =?UTF-8?Q?Beno=c3=aet_TELLIER?= <" + address + ">")
+            .map(Arguments::of);
+    };
+
+    @Test
+    void serviceShouldAttachJsonWithTheSenderAsReplyToAttributeValueWhenReplyToIsInvalid() throws Exception {
+        testee.init(FakeMailetConfig.builder().build());
+
+        byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
+        Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics));
+        ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar);
+        ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics);
+        MailAddress recipient = MailAddressFixture.ANY_AT_JAMES2;
+        Mail mail = FakeMail.builder()
+            .name("mail")
+            .sender(SENDER)
+            .recipient(recipient)
+            .attribute(new Attribute(ICALToJsonAttribute.DEFAULT_SOURCE, AttributeValue.ofAny(icals)))
+            .attribute(new Attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE, AttributeValue.ofAny(rawIcals)))
+            .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+                .addHeader(ICALToJsonAttribute.REPLY_TO_HEADER_NAME, "inv@lid.m@il.adr"))
+            .build();
+        testee.service(mail);
+
+        assertThat(AttributeUtils.getValueAndCastFromMail(mail, ICALToJsonAttribute.DEFAULT_DESTINATION, MAP_STRING_BYTES_CLASS))
+            .isPresent()
+            .hasValueSatisfying(jsons -> {
+                assertThat(jsons).hasSize(1);
+                assertThatJson(new String(jsons.values().iterator().next(), StandardCharsets.UTF_8))
+                    .isEqualTo("{" +
+                        "\"ical\": \"" + toJsonValue(ics) + "\"," +
+                        "\"sender\": \"" + SENDER.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
+                        "\"recipient\": \"" + recipient.asString() + "\"," +
+                        "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
+                        "\"sequence\": \"0\"," +
+                        "\"dtstamp\": \"20170106T115036Z\"," +
+                        "\"method\": \"REQUEST\"," +
+                        "\"recurrence-id\": null" +
+                        "}");
+            });
+    }
+
     @Test
-    public void serviceShouldAttachJsonForSeveralRecipient() throws Exception {
+    void serviceShouldAttachJsonForSeveralRecipient() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -311,6 +400,7 @@ public class ICALToJsonAttributeTest {
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + MailAddressFixture.ANY_AT_JAMES2.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
                         "\"dtstamp\": \"20170106T115036Z\"," +
@@ -321,6 +411,7 @@ public class ICALToJsonAttributeTest {
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + MailAddressFixture.OTHER_AT_JAMES.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
                         "\"dtstamp\": \"20170106T115036Z\"," +
@@ -331,7 +422,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldAttachJsonForSeveralICALs() throws Exception {
+    void serviceShouldAttachJsonForSeveralICALs() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -360,6 +451,7 @@ public class ICALToJsonAttributeTest {
                         "\"ical\": \"" + toJsonValue(ics2) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + recipient.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047feb2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe\"," +
                         "\"sequence\": \"0\"," +
                         "\"dtstamp\": \"20170103T103250Z\"," +
@@ -370,6 +462,7 @@ public class ICALToJsonAttributeTest {
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + recipient.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
                         "\"dtstamp\": \"20170106T115036Z\"," +
@@ -380,7 +473,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldFilterInvalidICS() throws Exception {
+    void serviceShouldFilterInvalidICS() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -409,6 +502,7 @@ public class ICALToJsonAttributeTest {
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + recipient.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
                         "\"dtstamp\": \"20170106T115036Z\"," +
@@ -419,7 +513,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldFilterNonExistingKeys() throws Exception {
+    void serviceShouldFilterNonExistingKeys() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -447,6 +541,7 @@ public class ICALToJsonAttributeTest {
                     assertThatJson(actual.get(0)).isEqualTo("{" +
                         "\"ical\": \"" + toJsonValue(ics) + "\"," +
                         "\"sender\": \"" + SENDER.asString() + "\"," +
+                        "\"replyTo\": \"" + SENDER.asString() + "\"," +
                         "\"recipient\": \"" + recipient.asString() + "\"," +
                         "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                         "\"sequence\": \"0\"," +
@@ -458,7 +553,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldUseFromWhenSpecified() throws Exception {
+    void serviceShouldUseFromWhenSpecified() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -487,6 +582,7 @@ public class ICALToJsonAttributeTest {
                             "\"ical\": \"" + toJsonValue(ics) + "\"," +
                             "\"sender\": \"" + from + "\"," +
                             "\"recipient\": \"" + recipient.asString() + "\"," +
+                            "\"replyTo\": \"" + from + "\"," +
                             "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                             "\"sequence\": \"0\"," +
                             "\"dtstamp\": \"20170106T115036Z\"," +
@@ -497,7 +593,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldSupportMimeMessagesWithoutFromFields() throws Exception {
+    void serviceShouldSupportMimeMessagesWithoutFromFields() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -524,6 +620,7 @@ public class ICALToJsonAttributeTest {
                             "\"ical\": \"" + toJsonValue(ics) + "\"," +
                             "\"sender\": \"" + SENDER.asString() + "\"," +
                             "\"recipient\": \"" + recipient.asString() + "\"," +
+                            "\"replyTo\": \"" + SENDER.asString() + "\"," +
                             "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                             "\"sequence\": \"0\"," +
                             "\"dtstamp\": \"20170106T115036Z\"," +
@@ -534,7 +631,7 @@ public class ICALToJsonAttributeTest {
     }
 
     @Test
-    public void serviceShouldUseFromWhenSpecifiedAndNoSender() throws Exception {
+    void serviceShouldUseFromWhenSpecifiedAndNoSender() throws Exception {
         testee.init(FakeMailetConfig.builder().build());
 
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
@@ -562,6 +659,7 @@ public class ICALToJsonAttributeTest {
                             "\"ical\": \"" + toJsonValue(ics) + "\"," +
                             "\"sender\": \"" + from + "\"," +
                             "\"recipient\": \"" + recipient.asString() + "\"," +
+                            "\"replyTo\": \"" + from + "\"," +
                             "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," +
                             "\"sequence\": \"0\"," +
                             "\"dtstamp\": \"20170106T115036Z\"," +
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/ICALAttributeDTOTest.java
similarity index 74%
rename from mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java
rename to mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALAttributeDTOTest.java
index dc530a1..5c69a99 100644
--- 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/ICALAttributeDTOTest.java
@@ -36,7 +36,7 @@ import net.fortuna.ical4j.data.CalendarBuilder;
 import net.fortuna.ical4j.model.Calendar;
 import nl.jqno.equalsverifier.EqualsVerifier;
 
-public class ICALTest {
+public class ICALAttributeDTOTest {
 
     @BeforeClass
     public static void setUpIcal4J() {
@@ -47,54 +47,17 @@ public class ICALTest {
     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.asString())
-            .build();
-    }
-
-    @Test
-    public void buildShouldFailWhenNoSender() throws Exception {
-        expectedException.expect(NullPointerException.class);
-
-        byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
-        Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics));
-
-        ICAL.builder()
-            .recipient(MailAddressFixture.ANY_AT_JAMES)
-            .from(calendar, ics)
-            .build();
-    }
-
-    @Test
-    public void buildShouldFailWhenNoRecipient() throws Exception {
-        expectedException.expect(NullPointerException.class);
-
-        byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
-        Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics));
-
-        ICAL.builder()
-            .sender(MailAddressFixture.OTHER_AT_JAMES.asString())
-            .from(calendar, ics)
-            .build();
-    }
-
-
-    @Test
     public void buildShouldWork() throws Exception {
         byte[] ics = ClassLoaderUtils.getSystemResourceAsByteArray("ics/meeting.ics");
         Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics));
 
         MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
         MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
-        ICAL ical = ICAL.builder()
-            .recipient(recipient)
-            .sender(sender.asString())
+        ICALAttributeDTO ical = ICALAttributeDTO.builder()
             .from(calendar, ics)
-            .build();
+            .sender(sender)
+            .recipient(recipient)
+            .replyTo(sender);
 
         assertThat(ical.getRecipient()).isEqualTo(recipient.asString());
         assertThat(ical.getSender()).isEqualTo(sender.asString());
@@ -110,7 +73,7 @@ public class ICALTest {
 
     @Test
     public void equalsAndHashCodeShouldBeWellImplemented() {
-        EqualsVerifier.forClass(ICAL.class).verify();
+        EqualsVerifier.forClass(ICALAttributeDTO.class).verify();
     }
 
     @Test
@@ -122,11 +85,11 @@ public class ICALTest {
 
         MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
         MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
-        ICAL.builder()
-            .recipient(recipient)
-            .sender(sender.asString())
+        ICALAttributeDTO.builder()
             .from(calendar, ics)
-            .build();
+            .sender(sender)
+            .recipient(recipient)
+            .replyTo(sender);
     }
 
     @Test
@@ -138,11 +101,11 @@ public class ICALTest {
 
         MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
         MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
-        ICAL.builder()
-            .recipient(recipient)
-            .sender(sender.asString())
+        ICALAttributeDTO.builder()
             .from(calendar, ics)
-            .build();
+            .sender(sender)
+            .recipient(recipient)
+            .replyTo(sender);
     }
 
     @Test
@@ -154,11 +117,11 @@ public class ICALTest {
 
         MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
         MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
-        ICAL.builder()
-            .recipient(recipient)
-            .sender(sender.asString())
+        ICALAttributeDTO.builder()
             .from(calendar, ics)
-            .build();
+            .sender(sender)
+            .recipient(recipient)
+            .replyTo(sender);
     }
 
     @Test
@@ -168,12 +131,12 @@ public class ICALTest {
 
         MailAddress recipient = MailAddressFixture.ANY_AT_JAMES;
         MailAddress sender = MailAddressFixture.OTHER_AT_JAMES;
-        ICAL ical = ICAL.builder()
-            .recipient(recipient)
-            .sender(sender.asString())
+        ICALAttributeDTO ical = ICALAttributeDTO.builder()
             .from(calendar, ics)
-            .build();
+            .sender(sender)
+            .recipient(recipient)
+            .replyTo(sender);
 
-        assertThat(ical.getSequence()).isEqualTo(ICAL.DEFAULT_SEQUENCE_VALUE);
+        assertThat(ical.getSequence()).isEqualTo(ICALAttributeDTO.DEFAULT_SEQUENCE_VALUE);
     }
 }


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