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 2016/03/01 16:00:33 UTC

[1/9] james-project git commit: JAMES-1692 add unit tests for SetMessagesMethod's delegates

Repository: james-project
Updated Branches:
  refs/heads/master 00678d881 -> 3b09ad753


JAMES-1692 add unit tests for SetMessagesMethod's delegates

... that were recently added or extracted


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

Branch: refs/heads/master
Commit: 9388764f9196879ef8befc047b28e4f17acde9a4
Parents: b21eae3
Author: Fabien Vignon <fv...@linagora.com>
Authored: Wed Feb 17 19:36:15 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:42:53 2016 +0100

----------------------------------------------------------------------
 .../jmap/methods/MIMEMessageConverterTest.java  | 109 ++++++++++++++++++
 .../SetMessagesCreationProcessorTest.java       | 113 +++++++++++++------
 .../methods/SetMessagesUpdateProcessorTest.java |  85 ++++++++++++++
 .../UpdateMessagePatchConverterTest.java        |  73 ++++++++++++
 4 files changed, 346 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/9388764f/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
new file mode 100644
index 0000000..af7d17a
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
@@ -0,0 +1,109 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.sql.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.Emailer;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.stream.Field;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class MIMEMessageConverterTest {
+    @Test
+    public void convertToMimeShouldAddInReplyToHeaderWhenProvided() {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        String matchingMessageId = "unique-message-id";
+        CreationMessage messageHavingInReplyTo = CreationMessage.builder()
+                .inReplyToMessageId(matchingMessageId)
+                .mailboxIds(ImmutableList.of("Hey!"))
+                .subject("subject")
+                .build();
+
+        // When
+        Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
+                "user|mailbox|1", messageHavingInReplyTo));
+
+        // Then
+        assertThat(result.getHeader().getFields("In-Reply-To")).extracting(Field::getBody)
+                .containsOnly(matchingMessageId);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void convertToMimeShouldThrowWhenMessageIsNull() {
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        sut.convertToMime(new MessageWithId.CreationMessageEntry("any", null));
+    }
+
+    @Test
+    public void convertToMimeShouldSetBothFromAndSenderHeaders() {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        String joesEmail = "joe@example.com";
+        CreationMessage testMessage = CreationMessage.builder()
+                .mailboxIds(ImmutableList.of("deadbeef-dead-beef-dead-beef"))
+                .subject("subject")
+                .from(Emailer.builder().email(joesEmail).name("joe").build())
+                .build();
+
+        // When
+        Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
+                "user|mailbox|1", testMessage));
+
+        // Then
+        assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.toString().equals(joesEmail));
+        assertThat(result.getSender().getAddress()).isEqualTo(joesEmail);
+    }
+
+    @Test
+    public void convertToMimeShouldSetCorrectLocalDate() {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        Instant now = Instant.now();
+        ZonedDateTime messageDate = ZonedDateTime.ofInstant(now, ZoneId.systemDefault());
+
+        CreationMessage testMessage = CreationMessage.builder()
+                .mailboxIds(ImmutableList.of("dead-bada55"))
+                .subject("subject")
+                .date(messageDate)
+                .build();
+
+        // When
+        Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
+                "user|mailbox|1", testMessage));
+
+        // Then
+        assertThat(result.getDate()).isEqualToIgnoringMillis(Date.from(now));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/9388764f/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
index 0b73ee2..06dc7c4 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -21,13 +21,16 @@ package org.apache.james.jmap.methods;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.time.ZonedDateTime;
 import java.util.Optional;
 import java.util.function.Function;
 
+import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.Emailer;
 import org.apache.james.jmap.model.Message;
@@ -37,9 +40,10 @@ import org.apache.james.jmap.model.SetMessagesResponse;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.mailbox.store.TestId;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
-import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -47,13 +51,25 @@ import com.google.common.collect.ImmutableMap;
 
 public class SetMessagesCreationProcessorTest {
 
+    private static final Message FAKE_MESSAGE = Message.builder()
+            .id(MessageId.of("user|outbox|1"))
+            .blobId("anything")
+            .threadId("anything")
+            .mailboxIds(ImmutableList.of("mailboxId"))
+            .headers(ImmutableMap.of())
+            .subject("anything")
+            .size(0)
+            .date(ZonedDateTime.now())
+            .preview("anything")
+            .build();
+
     @Test
-    @SuppressWarnings("unchecked")
     public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() {
-        SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, null, null) {
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null) {
             @Override
-            protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException {
-                Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class);
+            protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
+                @SuppressWarnings("unchecked")
+				Mailbox<TestId> fakeOutbox = (Mailbox<TestId>) mock(Mailbox.class);
                 when(fakeOutbox.getName()).thenReturn("outbox");
                 return Optional.of(fakeOutbox);
             }
@@ -79,26 +95,75 @@ public class SetMessagesCreationProcessorTest {
     @Test
     @SuppressWarnings("unchecked")
     public void processShouldReturnNonEmptyCreatedWhenRequestHasNonEmptyCreate() throws MailboxException {
-
-        MessageMapper<MailboxId> stubMapper = mock(MessageMapper.class);
-        MailboxSessionMapperFactory<MailboxId> mockSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
+        // Given
+        MessageMapper<TestId> stubMapper = mock(MessageMapper.class);
+        MailboxSessionMapperFactory<TestId> mockSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
         when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
                 .thenReturn(stubMapper);
 
-        SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, mockSessionMapperFactory, null) {
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, mockSessionMapperFactory, null) {
+            @Override
+            protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<TestId> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
+                return new MessageWithId<>(createdEntry.getCreationId(), FAKE_MESSAGE);
+            }
+            @Override
+            protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
+                Mailbox<TestId> fakeOutbox = mock(Mailbox.class);
+                when(fakeOutbox.getName()).thenReturn("outbox");
+                return Optional.of(fakeOutbox);
+            }
+        };
+        // When
+        SetMessagesResponse result = sut.process(buildFakeCreationRequest(), buildStubbedSession());
+
+        // Then
+        assertThat(result.getCreated()).isNotEmpty();
+        assertThat(result.getNotCreated()).isEmpty();
+    }
+
+    @Test(expected = MailboxRoleNotFoundException.class)
+    public void processShouldThrowWhenOutboxNotFound() {
+        // Given
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null) {
             @Override
-            protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<MailboxId> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
-                return new MessageWithId<>(createdEntry.creationId, getFakeMessage());
+            protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
+                return Optional.empty();
             }
+        };
+        // When
+        sut.process(buildFakeCreationRequest(), null);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void processShouldCallMessageMapperWhenRequestHasNonEmptyCreate() throws MailboxException {
+        // Given
+        Mailbox<TestId> fakeOutbox = mock(Mailbox.class);
+        MessageMapper<TestId> mockMapper = mock(MessageMapper.class);
+        MailboxSessionMapperFactory<TestId> stubSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
+        when(stubSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
+                .thenReturn(mockMapper);
+
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null,
+                stubSessionMapperFactory, new MIMEMessageConverter()) {
             @Override
-            protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException {
-                Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class);
+            protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
+                TestId stubMailboxId = mock(TestId.class);
+                when(stubMailboxId.serialize()).thenReturn("user|outbox|12345");
+                when(fakeOutbox.getMailboxId()).thenReturn(stubMailboxId);
                 when(fakeOutbox.getName()).thenReturn("outbox");
                 return Optional.of(fakeOutbox);
             }
         };
+        // When
+        sut.process(buildFakeCreationRequest(), buildStubbedSession());
+
+        // Then
+        verify(mockMapper).add(eq(fakeOutbox), any(MailboxMessage.class));
+    }
 
-        SetMessagesRequest creationRequest = SetMessagesRequest.builder()
+    private SetMessagesRequest buildFakeCreationRequest() {
+        return SetMessagesRequest.builder()
                 .create(ImmutableMap.of("anything-really", CreationMessage.builder()
                     .from(Emailer.builder().name("alice").email("alice@example.com").build())
                     .to(ImmutableList.of(Emailer.builder().name("bob").email("bob@example.com").build()))
@@ -106,26 +171,6 @@ public class SetMessagesCreationProcessorTest {
                     .mailboxIds(ImmutableList.of("mailboxId"))
                     .build()
                 ))
-                .build()
-                ;
-
-        SetMessagesResponse result = sut.process(creationRequest, buildStubbedSession());
-
-        assertThat(result.getCreated()).isNotEmpty();
-        assertThat(result.getNotCreated()).isEmpty();
-    }
-
-    private Message getFakeMessage() {
-        return Message.builder()
-                .id(org.apache.james.jmap.model.MessageId.of("user|outbox|1"))
-                .blobId("anything")
-                .threadId("anything")
-                .mailboxIds(ImmutableList.of("mailboxId"))
-                .headers(ImmutableMap.of())
-                .subject("anything")
-                .size(0)
-                .date(ZonedDateTime.now())
-                .preview("anything")
                 .build();
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/9388764f/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java
new file mode 100644
index 0000000..9a625c3
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java
@@ -0,0 +1,85 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.james.jmap.model.MessageId;
+import org.apache.james.jmap.model.MessageProperties;
+import org.apache.james.jmap.model.SetMessagesRequest;
+import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.jmap.model.UpdateMessagePatch;
+import org.apache.james.mailbox.store.TestId;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class SetMessagesUpdateProcessorTest {
+
+    @Test
+    public void processShouldReturnEmptyUpdatedWhenRequestHasEmptyUpdate() {
+        SetMessagesUpdateProcessor<TestId> sut = new SetMessagesUpdateProcessor<>(null, null, null);
+        SetMessagesRequest requestWithEmptyUpdate = SetMessagesRequest.builder().build();
+
+        SetMessagesResponse result = sut.process(requestWithEmptyUpdate, null);
+
+        assertThat(result.getUpdated()).isEmpty();
+        assertThat(result.getNotUpdated()).isEmpty();
+    }
+
+    @Test
+    public void processShouldReturnNonEmptyNotUpdatedWhenRequestHasInvalidUpdate() {
+        // Given
+        UpdateMessagePatchConverter mockConverter = mock(UpdateMessagePatchConverter.class);
+        UpdateMessagePatch mockInvalidPatch = mock(UpdateMessagePatch.class);
+        when(mockInvalidPatch.isValid()).thenReturn(false);
+
+        MessageProperties.MessageProperty invalidProperty = MessageProperties.MessageProperty.from;
+        ImmutableList<ValidationResult> nonEmptyValidationResult = ImmutableList.of(ValidationResult.builder()
+                .property(invalidProperty.toString()).build());
+        when(mockInvalidPatch.getValidationErrors())
+                .thenReturn(nonEmptyValidationResult);
+        when(mockConverter.fromJsonNode(any(ObjectNode.class)))
+                .thenReturn(mockInvalidPatch);
+
+        SetMessagesUpdateProcessor<TestId> sut = new SetMessagesUpdateProcessor<>(mockConverter, null, null);
+        MessageId requestMessageId = MessageId.of("user|inbox|1");
+        SetMessagesRequest requestWithInvalidUpdate = SetMessagesRequest.builder()
+                .update(ImmutableMap.of(requestMessageId, JsonNodeFactory.instance.objectNode()))
+                .build();
+
+        // When
+        SetMessagesResponse result = sut.process(requestWithInvalidUpdate, null);
+
+        // Then
+        assertThat(result.getNotUpdated()).isNotEmpty().describedAs("NotUpdated should not be empty");
+        assertThat(result.getNotUpdated()).containsKey(requestMessageId);
+        assertThat(result.getNotUpdated().get(requestMessageId).getProperties()).isPresent();
+        assertThat(result.getNotUpdated().get(requestMessageId).getProperties().get()).contains(invalidProperty);
+        assertThat(result.getUpdated()).isEmpty();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/9388764f/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/UpdateMessagePatchConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/UpdateMessagePatchConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/UpdateMessagePatchConverterTest.java
new file mode 100644
index 0000000..f67c92a
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/UpdateMessagePatchConverterTest.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import org.apache.james.jmap.model.UpdateMessagePatch;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+
+public class UpdateMessagePatchConverterTest {
+
+    @Test
+    public void fromJsonNodeShouldSetValidationResultWhenPatchIsInvalid() {
+
+        UpdateMessagePatchValidator stubValidator = mock(UpdateMessagePatchValidator.class);
+        when(stubValidator.isValid(any(ObjectNode.class))).thenReturn(false);
+        ImmutableSet<ValidationResult> nonEmptyValidationResult = ImmutableSet.of(ValidationResult.builder().build());
+        when(stubValidator.validate(any(ObjectNode.class))).thenReturn(nonEmptyValidationResult);
+
+        UpdateMessagePatchConverter sut = new UpdateMessagePatchConverter(null, stubValidator);
+
+        ObjectNode dummynode = JsonNodeFactory.instance.objectNode();
+        UpdateMessagePatch result = sut.fromJsonNode(dummynode);
+
+        assertThat(result).extracting(UpdateMessagePatch::getValidationErrors)
+                .isNotEmpty();
+    }
+
+    @Test
+    public void fromJsonNodeShouldReturnNoErrorWhenPatchIsValid() {
+
+        UpdateMessagePatchValidator mockValidator = mock(UpdateMessagePatchValidator.class);
+        when(mockValidator.isValid(any(ObjectNode.class))).thenReturn(true);
+        when(mockValidator.validate(any(ObjectNode.class))).thenReturn(ImmutableSet.of());
+        verify(mockValidator, never()).validate(any(ObjectNode.class));
+        ObjectMapper jsonParser = new ObjectMapper();
+
+        UpdateMessagePatchConverter sut = new UpdateMessagePatchConverter(jsonParser, mockValidator);
+
+        ObjectNode dummynode = JsonNodeFactory.instance.objectNode();
+        UpdateMessagePatch result = sut.fromJsonNode(dummynode);
+
+        assertThat(result.getValidationErrors().isEmpty()).isTrue();
+    }
+
+}
\ No newline at end of file


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


[8/9] james-project git commit: JAMES-1692 Strong typing creationId in SetMessages requests/responses

Posted by ad...@apache.org.
JAMES-1692 Strong typing creationId in SetMessages requests/responses


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

Branch: refs/heads/master
Commit: 8174cb776ed611fc3208ae650fb604b2a5a945d0
Parents: 078ba1a
Author: Fabien Vignon <fv...@linagora.com>
Authored: Thu Feb 25 16:01:05 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:46:15 2016 +0100

----------------------------------------------------------------------
 .../jmap/methods/MIMEMessageConverter.java      |  5 +-
 .../james/jmap/methods/MessageWithId.java       |  9 +--
 .../methods/SetMessagesCreationProcessor.java   |  3 +-
 .../james/jmap/model/CreationMessageId.java     | 59 ++++++++++++++++++++
 .../james/jmap/model/SetMessagesRequest.java    | 10 ++--
 .../james/jmap/model/SetMessagesResponse.java   | 20 +++----
 .../jmap/methods/MIMEMessageConverterTest.java  |  9 +--
 .../SetMessagesCreationProcessorTest.java       |  3 +-
 .../jmap/model/SetMessagesResponseTest.java     | 16 +++---
 9 files changed, 99 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
index 783ced0..207a36d 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
@@ -30,6 +30,7 @@ import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.FieldParser;
@@ -86,7 +87,7 @@ public class MIMEMessageConverter {
         return message;
     }
 
-    private Header buildMimeHeaders(String creationId, CreationMessage newMessage) {
+    private Header buildMimeHeaders(CreationMessageId creationId, CreationMessage newMessage) {
         Header messageHeaders = new HeaderImpl();
 
         // add From: and Sender: headers
@@ -116,7 +117,7 @@ public class MIMEMessageConverter {
         // add Subject: header
         messageHeaders.addField(Fields.subject(newMessage.getSubject()));
         // set creation Id as MessageId: header
-        messageHeaders.addField(Fields.messageId(creationId));
+        messageHeaders.addField(Fields.messageId(creationId.getId()));
 
         // date(String fieldName, Date date, TimeZone zone)
         // note that date conversion probably lose milliseconds !

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
index fbc7105..0e4e172 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
@@ -20,18 +20,19 @@
 package org.apache.james.jmap.methods;
 
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessageId;
 
 public class MessageWithId<T> {
 
-    private String creationId;
+    private CreationMessageId creationId;
     private T message;
 
-    public MessageWithId(String creationId, T message) {
+    public MessageWithId(CreationMessageId creationId, T message) {
         this.creationId = creationId;
         this.message = message;
     }
 
-    public String getCreationId() {
+    public CreationMessageId getCreationId() {
         return creationId;
     }
 
@@ -40,7 +41,7 @@ public class MessageWithId<T> {
     }
 
     public static class CreationMessageEntry extends MessageWithId<CreationMessage> {
-        public CreationMessageEntry(String creationId, CreationMessage message) {
+        public CreationMessageEntry(CreationMessageId creationId, CreationMessage message) {
             super(creationId, message);
         }
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
index 1177e54..718734d 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
@@ -38,6 +38,7 @@ import javax.mail.util.SharedByteArrayInputStream;
 
 import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessageProperties;
@@ -114,7 +115,7 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
                 .build();
     }
 
-    private Map<String, SetError> handleCreationErrors(Predicate<CreationMessage> invalidMessagesTester,
+    private Map<CreationMessageId, SetError> handleCreationErrors(Predicate<CreationMessage> invalidMessagesTester,
                                                                   SetMessagesRequest request) {
         return request.getCreate().entrySet().stream()
                 .filter(e -> invalidMessagesTester.test(e.getValue()))

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessageId.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessageId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessageId.java
new file mode 100644
index 0000000..e23dd6a
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessageId.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.jmap.model;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.base.Preconditions;
+
+public class CreationMessageId {
+
+    public static CreationMessageId of(String creationMessageId) {
+        return new CreationMessageId(creationMessageId);
+    }
+
+    private final String id;
+
+    private CreationMessageId(String id) {
+        Preconditions.checkNotNull(id);
+        Preconditions.checkArgument(!id.isEmpty());
+        this.id = id;
+    }
+
+    @JsonValue
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof CreationMessageId) {
+            CreationMessageId other = (CreationMessageId) obj;
+            return Objects.equals(this.id, other.id);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
index 8e25ac5..096e9c6 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
@@ -48,7 +48,7 @@ public class SetMessagesRequest implements JmapRequest {
 
         private String accountId;
         private String ifInState;
-        private ImmutableMap.Builder<String, CreationMessage> create;
+        private ImmutableMap.Builder<CreationMessageId, CreationMessage> create;
         private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider;
 
         private ImmutableList.Builder<MessageId> destroy;
@@ -73,7 +73,7 @@ public class SetMessagesRequest implements JmapRequest {
             return this;
         }
 
-        public Builder create(Map<String, CreationMessage> creations) {
+        public Builder create(Map<CreationMessageId, CreationMessage> creations) {
             this.create.putAll(creations);
             return this;
         }
@@ -95,11 +95,11 @@ public class SetMessagesRequest implements JmapRequest {
 
     private final Optional<String> accountId;
     private final Optional<String> ifInState;
-    private final Map<String, CreationMessage> create;
+    private final Map<CreationMessageId, CreationMessage> create;
     private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update;
     private final List<MessageId> destroy;
 
-    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<String, CreationMessage> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
+    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<CreationMessageId, CreationMessage> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
         this.accountId = accountId;
         this.ifInState = ifInState;
         this.create = create;
@@ -115,7 +115,7 @@ public class SetMessagesRequest implements JmapRequest {
         return ifInState;
     }
 
-    public Map<String, CreationMessage> getCreate() {
+    public Map<CreationMessageId, CreationMessage> getCreate() {
         return create;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
index 004b11b..a536ba4 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
@@ -53,10 +53,10 @@ public class SetMessagesResponse implements Method.Response {
         private String accountId;
         private String oldState;
         private String newState;
-        private final ImmutableMap.Builder<String, Message> created;
+        private final ImmutableMap.Builder<CreationMessageId, Message> created;
         private final ImmutableList.Builder<MessageId> updated;
         private final ImmutableList.Builder<MessageId> destroyed;
-        private final ImmutableMap.Builder<String, SetError> notCreated;
+        private final ImmutableMap.Builder<CreationMessageId, SetError> notCreated;
         private final ImmutableMap.Builder<MessageId, SetError> notUpdated;
         private final ImmutableMap.Builder<MessageId, SetError> notDestroyed;
 
@@ -81,7 +81,7 @@ public class SetMessagesResponse implements Method.Response {
             throw new NotImplementedException();
         }
 
-        public Builder created(Map<String, Message> created) {
+        public Builder created(Map<CreationMessageId, Message> created) {
             this.created.putAll(created);
             return this;
         }
@@ -101,7 +101,7 @@ public class SetMessagesResponse implements Method.Response {
             return this;
         }
 
-        public Builder notCreated(Map<String, SetError> notCreated) {
+        public Builder notCreated(Map<CreationMessageId, SetError> notCreated) {
             this.notCreated.putAll(notCreated);
             return this;
         }
@@ -130,15 +130,15 @@ public class SetMessagesResponse implements Method.Response {
     private final String accountId;
     private final String oldState;
     private final String newState;
-    private final Map<String, Message> created;
+    private final Map<CreationMessageId, Message> created;
     private final List<MessageId> updated;
     private final List<MessageId> destroyed;
-    private final Map<String, SetError> notCreated;
+    private final Map<CreationMessageId, SetError> notCreated;
     private final Map<MessageId, SetError> notUpdated;
     private final Map<MessageId, SetError> notDestroyed;
 
-    @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, Map<String, Message> created, List<MessageId> updated, List<MessageId> destroyed,
-            Map<String, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) {
+    @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, Map<CreationMessageId, Message> created, List<MessageId> updated, List<MessageId> destroyed,
+            Map<CreationMessageId, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) {
         this.accountId = accountId;
         this.oldState = oldState;
         this.newState = newState;
@@ -166,7 +166,7 @@ public class SetMessagesResponse implements Method.Response {
     }
 
     @JsonSerialize
-    public Map<String, Message> getCreated() {
+    public Map<CreationMessageId, Message> getCreated() {
         return created;
     }
 
@@ -181,7 +181,7 @@ public class SetMessagesResponse implements Method.Response {
     }
 
     @JsonSerialize
-    public Map<String, SetError> getNotCreated() {
+    public Map<CreationMessageId, SetError> getNotCreated() {
         return notCreated;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
index 0224f49..b238dbf 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
@@ -28,6 +28,7 @@ import java.time.ZoneId;
 import java.time.ZonedDateTime;
 
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.address.Mailbox;
 import org.apache.james.mime4j.stream.Field;
@@ -51,7 +52,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
-                "user|mailbox|1", messageHavingInReplyTo));
+                CreationMessageId.of("user|mailbox|1"), messageHavingInReplyTo));
 
         // Then
         assertThat(result.getHeader().getFields("In-Reply-To")).extracting(Field::getBody)
@@ -62,7 +63,7 @@ public class MIMEMessageConverterTest {
     public void convertToMimeShouldThrowWhenMessageIsNull() {
         MIMEMessageConverter sut = new MIMEMessageConverter();
 
-        sut.convertToMime(new MessageWithId.CreationMessageEntry("any", null));
+        sut.convertToMime(new MessageWithId.CreationMessageEntry(CreationMessageId.of("any"), null));
     }
 
     @Test
@@ -79,7 +80,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
-                "user|mailbox|1", testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage));
 
         // Then
         assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.equals(joesEmail));
@@ -103,7 +104,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
-                "user|mailbox|1", testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage));
 
         // Then
         assertThat(result.getDate()).isEqualToIgnoringMillis(Date.from(now));

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
index c5c8db6..5978f0e 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -33,6 +33,7 @@ import java.util.function.Function;
 
 import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.SetMessagesRequest;
@@ -164,7 +165,7 @@ public class SetMessagesCreationProcessorTest {
 
     private SetMessagesRequest buildFakeCreationRequest() {
         return SetMessagesRequest.builder()
-                .create(ImmutableMap.of("anything-really", CreationMessage.builder()
+                .create(ImmutableMap.of(CreationMessageId.of("anything-really"), CreationMessage.builder()
                     .from(DraftEmailer.builder().name("alice").email("alice@example.com").build())
                     .to(ImmutableList.of(DraftEmailer.builder().name("bob").email("bob@example.com").build()))
                     .subject("Hey! ")

http://git-wip-us.apache.org/repos/asf/james-project/blob/8174cb77/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
index 373f1b3..631fa96 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
@@ -53,7 +53,7 @@ public class SetMessagesResponseTest {
     @Test
     public void builderShouldWork() {
         ZonedDateTime currentDate = ZonedDateTime.now();
-        ImmutableMap<String, Message> created = ImmutableMap.of("user|created|1",
+        ImmutableMap<CreationMessageId, Message> created = ImmutableMap.of(CreationMessageId.of("user|created|1"),
             Message.builder()
                 .id(MessageId.of("user|created|1"))
                 .blobId("blobId")
@@ -67,7 +67,7 @@ public class SetMessagesResponseTest {
                 .build());
         ImmutableList<MessageId> updated = ImmutableList.of(MessageId.of("user|updated|1"));
         ImmutableList<MessageId> destroyed = ImmutableList.of(MessageId.of("user|destroyed|1"));
-        ImmutableMap<String, SetError> notCreated = ImmutableMap.of("dead-beef-defec8", SetError.builder().type("created").build());
+        ImmutableMap<CreationMessageId, SetError> notCreated = ImmutableMap.of(CreationMessageId.of("dead-beef-defec8"), SetError.builder().type("created").build());
         ImmutableMap<MessageId, SetError> notUpdated = ImmutableMap.of(MessageId.of("user|update|2"), SetError.builder().type("updated").build());
         ImmutableMap<MessageId, SetError> notDestroyed  = ImmutableMap.of(MessageId.of("user|destroyed|3"), SetError.builder().type("destroyed").build());
         SetMessagesResponse expected = new SetMessagesResponse(null, null, null, created, updated, destroyed, notCreated, notUpdated, notDestroyed);
@@ -89,10 +89,10 @@ public class SetMessagesResponseTest {
         // Given
         SetMessagesResponse.Builder emptyBuilder = SetMessagesResponse.builder();
         SetMessagesResponse testee = SetMessagesResponse.builder()
-                .created(buildMessage("user|inbox|1"))
+                .created(buildMessage(CreationMessageId.of("user|inbox|1")))
                 .updated(ImmutableList.of(MessageId.of("user|inbox|2")))
                 .destroyed(ImmutableList.of(MessageId.of("user|inbox|3")))
-                .notCreated(ImmutableMap.of( "dead-beef-defec8", SetError.builder().type("type").build()))
+                .notCreated(ImmutableMap.of(CreationMessageId.of("dead-beef-defec8"), SetError.builder().type("type").build()))
                 .notUpdated(ImmutableMap.of(MessageId.of("user|inbox|5"), SetError.builder().type("type").build()))
                 .notDestroyed(ImmutableMap.of(MessageId.of("user|inbox|6"), SetError.builder().type("type").build()))
                 .build();
@@ -103,9 +103,9 @@ public class SetMessagesResponseTest {
         assertThat(emptyBuilder.build()).isEqualToComparingFieldByField(testee);
     }
 
-    private ImmutableMap<String, Message> buildMessage(String messageId) {
+    private ImmutableMap<CreationMessageId, Message> buildMessage(CreationMessageId messageId) {
         return ImmutableMap.of(messageId, Message.builder()
-                .id(MessageId.of(messageId))
+                .id(MessageId.of(messageId.getId()))
                 .blobId("blobId")
                 .threadId("threadId")
                 .mailboxIds(ImmutableList.of())
@@ -138,10 +138,10 @@ public class SetMessagesResponseTest {
     @Test
     public void mergeIntoShouldMergeCreatedLists() {
         // Given
-        String buildersCreatedMessageId = "user|inbox|1";
+        CreationMessageId buildersCreatedMessageId = CreationMessageId.of("user|inbox|1");
         SetMessagesResponse.Builder nonEmptyBuilder = SetMessagesResponse.builder()
                 .created(buildMessage(buildersCreatedMessageId));
-        String createdMessageId = "user|inbox|2";
+        CreationMessageId createdMessageId = CreationMessageId.of("user|inbox|2");
         SetMessagesResponse testee = SetMessagesResponse.builder()
                 .created(buildMessage(createdMessageId))
                 .build();


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


[7/9] james-project git commit: JAMES-1692 send messages requests validation is deferred

Posted by ad...@apache.org.
JAMES-1692 send messages requests validation is deferred

... to method handler processing
setMessages response property notCreated is a map of creationId (not MessageId)


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

Branch: refs/heads/master
Commit: 078ba1a6caae80a425778b3eb305f24f0df7568d
Parents: 8983dbb
Author: Fabien Vignon <fv...@linagora.com>
Authored: Wed Feb 24 17:43:24 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:46:13 2016 +0100

----------------------------------------------------------------------
 .../jmap/methods/SetMessagesMethodTest.java     | 162 +++++++++++++-
 .../jmap/methods/MIMEMessageConverter.java      |  27 ++-
 .../methods/SetMessagesCreationProcessor.java   |  48 ++++-
 .../james/jmap/methods/ValidationResult.java    |  24 +++
 .../james/jmap/model/CreationMessage.java       | 213 ++++++++++++++++---
 .../james/jmap/model/SetMessagesResponse.java   |  24 +--
 .../jmap/methods/MIMEMessageConverterTest.java  |  10 +-
 .../SetMessagesCreationProcessorTest.java       |   6 +-
 .../james/jmap/model/CreationMessageTest.java   |  87 ++++++++
 .../jmap/model/SetMessagesResponseTest.java     |   6 +-
 10 files changed, 538 insertions(+), 69 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
index 3acaf8c..70dcd8f 100644
--- a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
@@ -25,6 +25,7 @@ import static com.jayway.restassured.config.EncoderConfig.encoderConfig;
 import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.endsWith;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasKey;
@@ -68,10 +69,10 @@ public abstract class SetMessagesMethodTest {
     private static final String NAME = "[0][0]";
     private static final String ARGUMENTS = "[0][1]";
 
-    private TemporaryFolder temporaryFolder = new TemporaryFolder();
-    private EmbeddedElasticSearch embeddedElasticSearch = new EmbeddedElasticSearch();
-    private EmbeddedCassandra cassandra = EmbeddedCassandra.createStartServer();
-    private JmapServer jmapServer = jmapServer(temporaryFolder, embeddedElasticSearch, cassandra);
+    private final TemporaryFolder temporaryFolder = new TemporaryFolder();
+    private final EmbeddedElasticSearch embeddedElasticSearch = new EmbeddedElasticSearch();
+    private final EmbeddedCassandra cassandra = EmbeddedCassandra.createStartServer();
+    private final JmapServer jmapServer = jmapServer(temporaryFolder, embeddedElasticSearch, cassandra);
     private String outboxId;
 
     protected abstract JmapServer jmapServer(TemporaryFolder temporaryFolder, EmbeddedElasticSearch embeddedElasticSearch, EmbeddedCassandra cassandra);
@@ -743,4 +744,157 @@ public abstract class SetMessagesMethodTest {
                 .body(ARGUMENTS + ".list[0].subject", equalTo(messageSubject))
                 .log().ifValidationFails();
     }
+
+    @Test
+    public void setMessagesShouldRejectWhenSendingMessageHasNoValidAddress() {
+        String messageCreationId = "user|inbox|1";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"MAILER-DEAMON\", \"email\": \"postmaster@example.com\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com@example.com\"}]," +
+                "        \"cc\": [{ \"name\": \"ALICE\"}]," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+                .when()
+                .post("/jmap")
+                .then()
+                .log().ifValidationFails()
+                .statusCode(200)
+                .body(NAME, equalTo("messagesSet"))
+
+                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("no recipient address set"))
+                .body(ARGUMENTS + ".created", aMapWithSize(0));
+    }
+
+    @Test
+    public void setMessagesShouldRejectWhenSendingMessageHasMissingFrom() {
+        String messageCreationId = "user|inbox|1";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"cc\": [{ \"name\": \"ALICE\"}]," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+        .when()
+                .post("/jmap")
+        .then()
+                .log().ifValidationFails()
+                .statusCode(200)
+                .body(NAME, equalTo("messagesSet"))
+                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'from' address is mandatory"))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("from"))
+                .body(ARGUMENTS + ".created", aMapWithSize(0));
+    }
+
+    @Test
+    public void setMessagesShouldSucceedWhenSendingMessageWithOnlyFromAddress() {
+        String messageCreationId = "user|inbox|1";
+        String fromAddress = "postmaster@example.com";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"cc\": [{ \"name\": \"ALICE\"}]," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+        .when()
+                .post("/jmap")
+        .then()
+                .log().ifValidationFails()
+                .statusCode(200)
+                .body(NAME, equalTo("messagesSet"))
+                .body(ARGUMENTS + ".created", aMapWithSize(1))
+                .body(ARGUMENTS + ".created", hasKey(messageCreationId))
+                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress))
+                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress))
+                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress));
+    }
+
+    @Test
+    public void setMessagesShouldRejectWhenSendingMessageHasMissingSubject() {
+        String messageCreationId = "user|inbox|1";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"MAILER-DEAMON\", \"email\": \"postmaster@example.com\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"cc\": [{ \"name\": \"ALICE\"}]," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+        .when()
+                .post("/jmap")
+        .then()
+                .log().ifValidationFails()
+                .statusCode(200)
+                .body(NAME, equalTo("messagesSet"))
+                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("subject"))
+                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'subject' is missing"))
+                .body(ARGUMENTS + ".created", aMapWithSize(0));
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
index 05d0229..783ced0 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
@@ -19,15 +19,17 @@
 
 package org.apache.james.jmap.methods;
 
+import static org.apache.james.jmap.model.CreationMessage.DraftEmailer;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Date;
+import java.util.Optional;
 import java.util.TimeZone;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.apache.james.jmap.model.CreationMessage;
-import org.apache.james.jmap.model.Emailer;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.FieldParser;
@@ -88,27 +90,27 @@ public class MIMEMessageConverter {
         Header messageHeaders = new HeaderImpl();
 
         // add From: and Sender: headers
-        newMessage.getFrom().map(this::convertEmailToMimeHeader)
-                .map(Fields::from)
-                .ifPresent(f -> messageHeaders.addField(f));
-        newMessage.getFrom().map(this::convertEmailToMimeHeader)
-                .map(Fields::sender)
-                .ifPresent(f -> messageHeaders.addField(f));
+        Optional<Mailbox> fromAddress = newMessage.getFrom().filter(DraftEmailer::hasValidEmail).map(this::convertEmailToMimeHeader);
+        fromAddress.map(Fields::from).ifPresent(messageHeaders::addField);
+        fromAddress.map(Fields::sender).ifPresent(messageHeaders::addField);
 
         // add Reply-To:
         messageHeaders.addField(Fields.replyTo(newMessage.getReplyTo().stream()
-                .map(rt -> convertEmailToMimeHeader(rt))
+                .map(this::convertEmailToMimeHeader)
                 .collect(Collectors.toList())));
         // add To: headers
         messageHeaders.addField(Fields.to(newMessage.getTo().stream()
+                .filter(DraftEmailer::hasValidEmail)
                 .map(this::convertEmailToMimeHeader)
                 .collect(Collectors.toList())));
         // add Cc: headers
         messageHeaders.addField(Fields.cc(newMessage.getCc().stream()
+                .filter(DraftEmailer::hasValidEmail)
                 .map(this::convertEmailToMimeHeader)
                 .collect(Collectors.toList())));
         // add Bcc: headers
         messageHeaders.addField(Fields.bcc(newMessage.getBcc().stream()
+                .filter(DraftEmailer::hasValidEmail)
                 .map(this::convertEmailToMimeHeader)
                 .collect(Collectors.toList())));
         // add Subject: header
@@ -143,8 +145,11 @@ public class MIMEMessageConverter {
         }
     }
 
-    private Mailbox convertEmailToMimeHeader(Emailer address) {
-        String[] splitAddress = address.getEmail().split("@", 2);
-        return new Mailbox(address.getName(), null, splitAddress[0], splitAddress[1]);
+    private Mailbox convertEmailToMimeHeader(DraftEmailer address) {
+        if (!address.hasValidEmail()) {
+            throw new IllegalArgumentException("address");
+        }
+        CreationMessage.EmailUserAndDomain emailUserAndDomain = address.getEmailUserAndDomain();
+        return new Mailbox(address.getName().orElse(null), null, emailUserAndDomain.getUser().orElse(null), emailUserAndDomain.getDomain().orElse(null));
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
index 3cb11db..1177e54 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
@@ -19,9 +19,17 @@
 
 package org.apache.james.jmap.methods;
 
+import static org.apache.james.jmap.model.MessageProperties.MessageProperty;
+
+import java.util.AbstractMap;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.mail.Flags;
@@ -32,6 +40,8 @@ import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
+import org.apache.james.jmap.model.MessageProperties;
+import org.apache.james.jmap.model.SetError;
 import org.apache.james.jmap.model.SetMessagesRequest;
 import org.apache.james.jmap.model.SetMessagesResponse;
 import org.apache.james.jmap.model.mailbox.Role;
@@ -54,6 +64,7 @@ import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.functions.ThrowingFunction;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 
@@ -70,7 +81,8 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
     @VisibleForTesting
     SetMessagesCreationProcessor(MailboxMapperFactory<Id> mailboxMapperFactory,
                                  MailboxManager mailboxManager,
-                                 MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, MIMEMessageConverter mimeMessageConverter) {
+                                 MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory,
+                                 MIMEMessageConverter mimeMessageConverter) {
         this.mailboxMapperFactory = mailboxMapperFactory;
         this.mailboxManager = mailboxManager;
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
@@ -86,11 +98,43 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
             LOGGER.error("Unable to find a mailbox with role 'outbox'!");
             throw Throwables.propagate(e);
         }
+
+        // handle errors
+        Predicate<CreationMessage> validMessagesTester = CreationMessage::isValid;
+        Predicate<CreationMessage> invalidMessagesTester = validMessagesTester.negate();
+        SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder()
+                .notCreated(handleCreationErrors(invalidMessagesTester, request));
+
         return request.getCreate().entrySet().stream()
+                .filter(e -> validMessagesTester.test(e.getValue()))
                 .map(e -> new MessageWithId.CreationMessageEntry(e.getKey(), e.getValue()))
                 .map(nuMsg -> createMessageInOutbox(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox)))
                 .map(msg -> SetMessagesResponse.builder().created(ImmutableMap.of(msg.getCreationId(), msg.getMessage())).build())
-                .reduce(SetMessagesResponse.builder(), SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner)
+                .reduce(responseBuilder, SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner)
+                .build();
+    }
+
+    private Map<String, SetError> handleCreationErrors(Predicate<CreationMessage> invalidMessagesTester,
+                                                                  SetMessagesRequest request) {
+        return request.getCreate().entrySet().stream()
+                .filter(e -> invalidMessagesTester.test(e.getValue()))
+                .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), buildSetErrorFromValidationResult(e.getValue().validate())))
+                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
+    }
+
+    private SetError buildSetErrorFromValidationResult(List<ValidationResult> validationErrors) {
+        String formattedValidationErrorMessage = validationErrors.stream()
+                .map(err -> err.getProperty() + ": " + err.getErrorMessage())
+                .collect(Collectors.joining("\\n"));
+        Splitter propertiesSplitter = Splitter.on(',').trimResults().omitEmptyStrings();
+        Set<MessageProperties.MessageProperty> properties = validationErrors.stream()
+                .flatMap(err -> propertiesSplitter.splitToList(err.getProperty()).stream())
+                .flatMap(MessageProperty::find)
+                .collect(Collectors.toSet());
+        return SetError.builder()
+                .type("invalidProperties")
+                .properties(properties)
+                .description(formattedValidationErrorMessage)
                 .build();
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValidationResult.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValidationResult.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValidationResult.java
index b48c88f..9540ce6 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValidationResult.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValidationResult.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.jmap.methods;
 
+import java.util.Objects;
+
 import com.google.common.annotations.VisibleForTesting;
 
 public class ValidationResult {
@@ -66,4 +68,26 @@ public class ValidationResult {
         return errorMessage;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof ValidationResult) {
+            ValidationResult otherEMailer = (ValidationResult) o;
+            return Objects.equals(property, otherEMailer.property)
+                    && Objects.equals(errorMessage, otherEMailer.errorMessage);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(property, errorMessage);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(getClass())
+                .add("property", property)
+                .add("errorMessage", errorMessage)
+                .toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
index 697c9c7..4fac7e8 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
@@ -19,15 +19,20 @@
 
 package org.apache.james.jmap.model;
 
+import static org.apache.james.jmap.model.MessageProperties.MessageProperty;
+
 import java.time.ZonedDateTime;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
 
-import org.apache.james.jmap.methods.GetMessagesMethod;
-import org.apache.james.jmap.methods.JmapResponseWriterImpl;
+import org.apache.james.jmap.methods.ValidationResult;
 
-import com.fasterxml.jackson.annotation.JsonFilter;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.common.annotations.VisibleForTesting;
@@ -39,6 +44,10 @@ import com.google.common.collect.ImmutableMap;
 @JsonDeserialize(builder = CreationMessage.Builder.class)
 public class CreationMessage {
 
+    private static final String RECIPIENT_PROPERTY_NAMES = ImmutableList.of(MessageProperty.to, MessageProperty.cc, MessageProperty.bcc).stream()
+            .map(MessageProperty::asFieldName)
+            .collect(Collectors.joining(", "));
+
     public static Builder builder() {
         return new Builder();
     }
@@ -52,11 +61,11 @@ public class CreationMessage {
         private boolean isAnswered;
         private boolean isDraft;
         private final ImmutableMap.Builder<String, String> headers;
-        private Emailer from;
-        private final ImmutableList.Builder<Emailer> to;
-        private final ImmutableList.Builder<Emailer> cc;
-        private final ImmutableList.Builder<Emailer> bcc;
-        private final ImmutableList.Builder<Emailer> replyTo;
+        private Optional<DraftEmailer> from = Optional.empty();
+        private final ImmutableList.Builder<DraftEmailer> to;
+        private final ImmutableList.Builder<DraftEmailer> cc;
+        private final ImmutableList.Builder<DraftEmailer> bcc;
+        private final ImmutableList.Builder<DraftEmailer> replyTo;
         private String subject;
         private ZonedDateTime date;
         private String textBody;
@@ -109,27 +118,27 @@ public class CreationMessage {
             return this;
         }
 
-        public Builder from(Emailer from) {
-            this.from = from;
+        public Builder from(DraftEmailer from) {
+            this.from = Optional.ofNullable(from);
             return this;
         }
 
-        public Builder to(List<Emailer> to) {
+        public Builder to(List<DraftEmailer> to) {
             this.to.addAll(to);
             return this;
         }
 
-        public Builder cc(List<Emailer> cc) {
+        public Builder cc(List<DraftEmailer> cc) {
             this.cc.addAll(cc);
             return this;
         }
 
-        public Builder bcc(List<Emailer> bcc) {
+        public Builder bcc(List<DraftEmailer> bcc) {
             this.bcc.addAll(bcc);
             return this;
         }
 
-        public Builder replyTo(List<Emailer> replyTo) {
+        public Builder replyTo(List<DraftEmailer> replyTo) {
             this.replyTo.addAll(replyTo);
             return this;
         }
@@ -173,7 +182,6 @@ public class CreationMessage {
         public CreationMessage build() {
             Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory");
             Preconditions.checkState(headers != null, "'headers' is mandatory");
-            Preconditions.checkState(!Strings.isNullOrEmpty(subject), "'subject' is mandatory");
             ImmutableList<Attachment> attachments = this.attachments.build();
             ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
             Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachments'");
@@ -182,7 +190,7 @@ public class CreationMessage {
                 date = ZonedDateTime.now();
             }
 
-            return new CreationMessage(mailboxIds, Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, isDraft, headers.build(), Optional.ofNullable(from),
+            return new CreationMessage(mailboxIds, Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, isDraft, headers.build(), from,
                     to.build(), cc.build(), bcc.build(), replyTo.build(), subject, date, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody), attachments, attachedMessages);
         }
     }
@@ -194,11 +202,11 @@ public class CreationMessage {
     private final boolean isAnswered;
     private final boolean isDraft;
     private final ImmutableMap<String, String> headers;
-    private final Optional<Emailer> from;
-    private final ImmutableList<Emailer> to;
-    private final ImmutableList<Emailer> cc;
-    private final ImmutableList<Emailer> bcc;
-    private final ImmutableList<Emailer> replyTo;
+    private final Optional<DraftEmailer> from;
+    private final ImmutableList<DraftEmailer> to;
+    private final ImmutableList<DraftEmailer> cc;
+    private final ImmutableList<DraftEmailer> bcc;
+    private final ImmutableList<DraftEmailer> replyTo;
     private final String subject;
     private final ZonedDateTime date;
     private final Optional<String> textBody;
@@ -207,8 +215,8 @@ public class CreationMessage {
     private final ImmutableMap<String, SubMessage> attachedMessages;
 
     @VisibleForTesting
-    CreationMessage(ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, ImmutableMap<String, String> headers, Optional<Emailer> from,
-                    ImmutableList<Emailer> to, ImmutableList<Emailer> cc, ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments,
+    CreationMessage(ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, ImmutableMap<String, String> headers, Optional<DraftEmailer> from,
+                    ImmutableList<DraftEmailer> to, ImmutableList<DraftEmailer> cc, ImmutableList<DraftEmailer> bcc, ImmutableList<DraftEmailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments,
                     ImmutableMap<String, SubMessage> attachedMessages) {
         this.mailboxIds = mailboxIds;
         this.inReplyToMessageId = inReplyToMessageId;
@@ -258,23 +266,23 @@ public class CreationMessage {
         return headers;
     }
 
-    public Optional<Emailer> getFrom() {
+    public Optional<DraftEmailer> getFrom() {
         return from;
     }
 
-    public ImmutableList<Emailer> getTo() {
+    public ImmutableList<DraftEmailer> getTo() {
         return to;
     }
 
-    public ImmutableList<Emailer> getCc() {
+    public ImmutableList<DraftEmailer> getCc() {
         return cc;
     }
 
-    public ImmutableList<Emailer> getBcc() {
+    public ImmutableList<DraftEmailer> getBcc() {
         return bcc;
     }
 
-    public ImmutableList<Emailer> getReplyTo() {
+    public ImmutableList<DraftEmailer> getReplyTo() {
         return replyTo;
     }
 
@@ -302,4 +310,153 @@ public class CreationMessage {
         return attachedMessages;
     }
 
+    public boolean isValid() {
+        return validate().isEmpty();
+    }
+
+    public List<ValidationResult> validate() {
+        ImmutableList.Builder<ValidationResult> errors = ImmutableList.builder();
+        assertValidFromProvided(errors);
+        assertAtLeastOneValidRecipient(errors);
+        assertSubjectProvided(errors);
+        return errors.build();
+    }
+
+    private void assertSubjectProvided(ImmutableList.Builder<ValidationResult> errors) {
+        if (Strings.isNullOrEmpty(subject)) {
+            errors.add(ValidationResult.builder().message("'subject' is missing").property(MessageProperty.subject.asFieldName()).build());
+        }
+    }
+
+    private void assertAtLeastOneValidRecipient(ImmutableList.Builder<ValidationResult> errors) {
+        ImmutableList<DraftEmailer> recipients = ImmutableList.<DraftEmailer>builder().addAll(to).addAll(cc).addAll(bcc).build();
+        boolean hasAtLeastOneAddressToSendTo = recipients.stream().anyMatch(DraftEmailer::hasValidEmail);
+        boolean recipientsHaveValidAddresses = recipients.stream().allMatch(e1 -> e1.getEmail() != null);
+        if (!(recipientsHaveValidAddresses && hasAtLeastOneAddressToSendTo)) {
+            errors.add(ValidationResult.builder().message("no recipient address set").property(RECIPIENT_PROPERTY_NAMES).build());
+        }
+    }
+
+    private void assertValidFromProvided(ImmutableList.Builder<ValidationResult> errors) {
+        ValidationResult invalidPropertyFrom = ValidationResult.builder()
+                .property(MessageProperty.from.asFieldName())
+                .message("'from' address is mandatory")
+                .build();
+        if (!from.isPresent()) {
+            errors.add(invalidPropertyFrom);
+        }
+        from.filter(f -> !f.hasValidEmail()).ifPresent(f -> errors.add(invalidPropertyFrom));
+    }
+
+    @JsonDeserialize(builder = DraftEmailer.Builder.class)
+    public static class DraftEmailer {
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        @JsonPOJOBuilder(withPrefix = "")
+        public static class Builder {
+            private Optional<String> name = Optional.empty();
+            private Optional<String> email = Optional.empty();
+
+            public Builder name(String name) {
+                this.name = Optional.ofNullable(name);
+                return this;
+            }
+
+            public Builder email(String email) {
+                this.email = Optional.ofNullable(email);
+                return this;
+            }
+
+            public DraftEmailer build() {
+                return new DraftEmailer(name, email);
+            }
+        }
+
+        private final Optional<String> name;
+        private final Optional<String> email;
+        private final EmailValidator emailValidator;
+
+        @VisibleForTesting
+        DraftEmailer(Optional<String> name, Optional<String> email) {
+            this.name = name;
+            this.email = email;
+            this.emailValidator = new EmailValidator();
+        }
+
+        public Optional<String> getName() {
+            return name;
+        }
+
+        public Optional<String> getEmail() {
+            return email;
+        }
+
+        public boolean hasValidEmail() {
+            return getEmail().isPresent() && emailValidator.isValid(getEmail().get());
+        }
+
+        public EmailUserAndDomain getEmailUserAndDomain() {
+            String[] splitAddress = email.get().split("@", 2);
+            return new EmailUserAndDomain(Optional.ofNullable(splitAddress[0]), Optional.ofNullable(splitAddress[1]));
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof DraftEmailer) {
+                DraftEmailer otherEMailer = (DraftEmailer) o;
+                return Objects.equals(name, otherEMailer.name)
+                        && Objects.equals(email.orElse("<unset>"), otherEMailer.email.orElse("<unset>") );
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name, email);
+        }
+
+        @Override
+        public String toString() {
+            return com.google.common.base.Objects.toStringHelper(this)
+                    .add("name", name)
+                    .add("email", email.orElse("<unset>"))
+                    .toString();
+        }
+    }
+
+    public static class EmailUserAndDomain {
+        private final Optional<String> user;
+        private final Optional<String> domain;
+
+        public EmailUserAndDomain(Optional<String> user, Optional<String> domain) {
+            this.user = user;
+            this.domain = domain;
+        }
+
+        public Optional<String> getUser() {
+            return user;
+        }
+
+        public Optional<String> getDomain() {
+            return domain;
+        }
+    }
+
+    public static class EmailValidator {
+
+        public boolean isValid(String email) {
+            boolean result = true;
+            try {
+                InternetAddress emailAddress = new InternetAddress(email);
+                // verrrry permissive validator !
+                emailAddress.validate();
+            } catch (AddressException ex) {
+                result = false;
+            }
+            return result;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
index c096c8a..004b11b 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
@@ -20,17 +20,15 @@ package org.apache.james.jmap.model;
 
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiFunction;
 
-import com.google.common.base.Strings;
 import org.apache.commons.lang.NotImplementedException;
-import org.apache.james.jmap.methods.MessageWithId;
 import org.apache.james.jmap.methods.Method;
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
@@ -55,12 +53,12 @@ public class SetMessagesResponse implements Method.Response {
         private String accountId;
         private String oldState;
         private String newState;
-        private ImmutableMap.Builder<String, Message> created;
-        private ImmutableList.Builder<MessageId> updated;
-        private ImmutableList.Builder<MessageId> destroyed;
-        private ImmutableMap.Builder<MessageId, SetError> notCreated;
-        private ImmutableMap.Builder<MessageId, SetError> notUpdated;
-        private ImmutableMap.Builder<MessageId, SetError> notDestroyed;
+        private final ImmutableMap.Builder<String, Message> created;
+        private final ImmutableList.Builder<MessageId> updated;
+        private final ImmutableList.Builder<MessageId> destroyed;
+        private final ImmutableMap.Builder<String, SetError> notCreated;
+        private final ImmutableMap.Builder<MessageId, SetError> notUpdated;
+        private final ImmutableMap.Builder<MessageId, SetError> notDestroyed;
 
         private Builder() {
             created = ImmutableMap.builder();
@@ -103,7 +101,7 @@ public class SetMessagesResponse implements Method.Response {
             return this;
         }
 
-        public Builder notCreated(Map<MessageId, SetError> notCreated) {
+        public Builder notCreated(Map<String, SetError> notCreated) {
             this.notCreated.putAll(notCreated);
             return this;
         }
@@ -135,12 +133,12 @@ public class SetMessagesResponse implements Method.Response {
     private final Map<String, Message> created;
     private final List<MessageId> updated;
     private final List<MessageId> destroyed;
-    private final Map<MessageId, SetError> notCreated;
+    private final Map<String, SetError> notCreated;
     private final Map<MessageId, SetError> notUpdated;
     private final Map<MessageId, SetError> notDestroyed;
 
     @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, Map<String, Message> created, List<MessageId> updated, List<MessageId> destroyed,
-            Map<MessageId, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) {
+            Map<String, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) {
         this.accountId = accountId;
         this.oldState = oldState;
         this.newState = newState;
@@ -183,7 +181,7 @@ public class SetMessagesResponse implements Method.Response {
     }
 
     @JsonSerialize
-    public Map<MessageId, SetError> getNotCreated() {
+    public Map<String, SetError> getNotCreated() {
         return notCreated;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
index af7d17a..0224f49 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.methods;
 
+import static org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.sql.Date;
@@ -27,7 +28,6 @@ import java.time.ZoneId;
 import java.time.ZonedDateTime;
 
 import org.apache.james.jmap.model.CreationMessage;
-import org.apache.james.jmap.model.Emailer;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.address.Mailbox;
 import org.apache.james.mime4j.stream.Field;
@@ -43,8 +43,9 @@ public class MIMEMessageConverterTest {
 
         String matchingMessageId = "unique-message-id";
         CreationMessage messageHavingInReplyTo = CreationMessage.builder()
+                .from(DraftEmailer.builder().name("sender").build())
                 .inReplyToMessageId(matchingMessageId)
-                .mailboxIds(ImmutableList.of("Hey!"))
+                .mailboxIds(ImmutableList.of("dead-beef-1337"))
                 .subject("subject")
                 .build();
 
@@ -73,7 +74,7 @@ public class MIMEMessageConverterTest {
         CreationMessage testMessage = CreationMessage.builder()
                 .mailboxIds(ImmutableList.of("deadbeef-dead-beef-dead-beef"))
                 .subject("subject")
-                .from(Emailer.builder().email(joesEmail).name("joe").build())
+                .from(DraftEmailer.builder().email(joesEmail).name("joe").build())
                 .build();
 
         // When
@@ -81,7 +82,7 @@ public class MIMEMessageConverterTest {
                 "user|mailbox|1", testMessage));
 
         // Then
-        assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.toString().equals(joesEmail));
+        assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.equals(joesEmail));
         assertThat(result.getSender().getAddress()).isEqualTo(joesEmail);
     }
 
@@ -96,6 +97,7 @@ public class MIMEMessageConverterTest {
         CreationMessage testMessage = CreationMessage.builder()
                 .mailboxIds(ImmutableList.of("dead-bada55"))
                 .subject("subject")
+                .from(DraftEmailer.builder().name("sender").build())
                 .date(messageDate)
                 .build();
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
index 06dc7c4..c5c8db6 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.methods;
 
+import static org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -32,7 +33,6 @@ import java.util.function.Function;
 
 import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
-import org.apache.james.jmap.model.Emailer;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.SetMessagesRequest;
@@ -165,8 +165,8 @@ public class SetMessagesCreationProcessorTest {
     private SetMessagesRequest buildFakeCreationRequest() {
         return SetMessagesRequest.builder()
                 .create(ImmutableMap.of("anything-really", CreationMessage.builder()
-                    .from(Emailer.builder().name("alice").email("alice@example.com").build())
-                    .to(ImmutableList.of(Emailer.builder().name("bob").email("bob@example.com").build()))
+                    .from(DraftEmailer.builder().name("alice").email("alice@example.com").build())
+                    .to(ImmutableList.of(DraftEmailer.builder().name("bob").email("bob@example.com").build()))
                     .subject("Hey! ")
                     .mailboxIds(ImmutableList.of("mailboxId"))
                     .build()

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/CreationMessageTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/CreationMessageTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/CreationMessageTest.java
new file mode 100644
index 0000000..48fca1c
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/CreationMessageTest.java
@@ -0,0 +1,87 @@
+/****************************************************************
+ * 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.jmap.model;
+
+import static org.apache.james.jmap.model.CreationMessage.DraftEmailer;
+import static org.apache.james.jmap.model.MessageProperties.MessageProperty;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.jmap.methods.ValidationResult;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CreationMessageTest {
+
+    private CreationMessage.Builder testedBuilder;
+
+    @Before
+    public void setUp() {
+        testedBuilder = CreationMessage.builder()
+                .mailboxIds(ImmutableList.of("ba9-0f-dead-beef"))
+                .headers(ImmutableMap.of())
+                .subject("anything");
+    }
+
+    @Test
+    public void validateShouldReturnErrorWhenFromIsMissing() {
+        CreationMessage sut = testedBuilder.build();
+
+        assertThat(sut.validate()).contains(ValidationResult.builder()
+                .message("'from' address is mandatory")
+                .property(MessageProperty.from.asFieldName())
+                .build()
+        );
+    }
+
+    @Test
+    public void validateShouldReturnErrorWhenFromIsInvalid() {
+        CreationMessage sut = testedBuilder.from(DraftEmailer.builder().name("bob").email("bob@domain.com@example.com").build()).build();
+
+        assertThat(sut.validate()).contains(ValidationResult.builder()
+                .message("'from' address is mandatory")
+                .property(MessageProperty.from.asFieldName())
+                .build()
+        );
+    }
+
+    @Test
+    public void validateShouldReturnErrorWhenNoRecipientSet () {
+        CreationMessage  sut = testedBuilder
+                .from(DraftEmailer.builder().name("bob").email("bob@example.com").build()).build();
+
+        assertThat(sut.validate()).extracting(ValidationResult::getErrorMessage).contains("no recipient address set");
+    }
+
+    @Test
+    public void validateShouldReturnErrorWhenNoValidRecipientSet () {
+        CreationMessage sut = testedBuilder
+                .from(DraftEmailer.builder().name("bob").email("bob@example.com").build())
+                .to(ImmutableList.of(DraftEmailer.builder().name("riri").email("riri@acme.com@example.com").build()))
+                .cc(ImmutableList.of(DraftEmailer.builder().name("fifi").email("fifi@acme.com@example.com").build()))
+                .bcc(ImmutableList.of(DraftEmailer.builder().name("loulou").email("loulou@acme.com@example.com").build()))
+                .build();
+
+        assertThat(sut.validate()).extracting(ValidationResult::getErrorMessage).contains("no recipient address set");
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/078ba1a6/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
index 9877651..373f1b3 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
@@ -23,8 +23,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.stream.Collectors;
 
 import org.apache.commons.lang.NotImplementedException;
 import org.junit.Test;
@@ -69,7 +67,7 @@ public class SetMessagesResponseTest {
                 .build());
         ImmutableList<MessageId> updated = ImmutableList.of(MessageId.of("user|updated|1"));
         ImmutableList<MessageId> destroyed = ImmutableList.of(MessageId.of("user|destroyed|1"));
-        ImmutableMap<MessageId, SetError> notCreated = ImmutableMap.of(MessageId.of("user|created|2"), SetError.builder().type("created").build());
+        ImmutableMap<String, SetError> notCreated = ImmutableMap.of("dead-beef-defec8", SetError.builder().type("created").build());
         ImmutableMap<MessageId, SetError> notUpdated = ImmutableMap.of(MessageId.of("user|update|2"), SetError.builder().type("updated").build());
         ImmutableMap<MessageId, SetError> notDestroyed  = ImmutableMap.of(MessageId.of("user|destroyed|3"), SetError.builder().type("destroyed").build());
         SetMessagesResponse expected = new SetMessagesResponse(null, null, null, created, updated, destroyed, notCreated, notUpdated, notDestroyed);
@@ -94,7 +92,7 @@ public class SetMessagesResponseTest {
                 .created(buildMessage("user|inbox|1"))
                 .updated(ImmutableList.of(MessageId.of("user|inbox|2")))
                 .destroyed(ImmutableList.of(MessageId.of("user|inbox|3")))
-                .notCreated(ImmutableMap.of( MessageId.of("user|inbox|4"), SetError.builder().type("type").build()))
+                .notCreated(ImmutableMap.of( "dead-beef-defec8", SetError.builder().type("type").build()))
                 .notUpdated(ImmutableMap.of(MessageId.of("user|inbox|5"), SetError.builder().type("type").build()))
                 .notDestroyed(ImmutableMap.of(MessageId.of("user|inbox|6"), SetError.builder().type("type").build()))
                 .build();


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


[3/9] james-project git commit: JAMES-1692 add SetMessagesCreationProcessor impl. for sending message

Posted by ad...@apache.org.
JAMES-1692 add SetMessagesCreationProcessor impl. for sending message

add CreationMessage to hold new messages.
build up MIME Messages for james to append to OUTBOX
extract SetMessagesProcessor<Id> interface


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

Branch: refs/heads/master
Commit: 99ac325d5f9bab58363a62b50a1ff7965611473b
Parents: 0625fb5
Author: Fabien Vignon <fv...@linagora.com>
Authored: Fri Feb 12 19:40:12 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:42:53 2016 +0100

----------------------------------------------------------------------
 .../org/apache/james/jmap/MethodsModule.java    |   2 +
 .../jmap/methods/SetMessagesMethodTest.java     |   2 -
 .../MailboxRoleNotFoundException.java           |  36 +++
 .../jmap/methods/MIMEMessageConverter.java      | 150 +++++++++
 .../james/jmap/methods/MessageWithId.java       |  48 +++
 .../methods/SetMessagesCreationProcessor.java   | 174 +++++++++++
 .../james/jmap/methods/SetMessagesMethod.java   |   9 +-
 .../jmap/methods/SetMessagesProcessor.java      |  29 ++
 .../methods/SetMessagesUpdateProcessor.java     |   4 +-
 .../james/jmap/model/CreationMessage.java       | 305 +++++++++++++++++++
 .../james/jmap/model/SetMessagesRequest.java    |  12 +-
 .../james/jmap/model/SetMessagesResponse.java   |  27 +-
 .../SetMessagesCreationProcessorTest.java       | 131 ++++++++
 .../jmap/model/SetMessagesResponseTest.java     |  11 +-
 14 files changed, 913 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
----------------------------------------------------------------------
diff --git a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
index 27faf5a..4e57d20 100644
--- a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
+++ b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
@@ -28,6 +28,7 @@ import org.apache.james.jmap.methods.JmapRequestParserImpl;
 import org.apache.james.jmap.methods.JmapResponseWriter;
 import org.apache.james.jmap.methods.JmapResponseWriterImpl;
 import org.apache.james.jmap.methods.Method;
+import org.apache.james.jmap.methods.SetMessagesCreationProcessor;
 import org.apache.james.jmap.methods.SetMessagesMethod;
 import org.apache.james.jmap.methods.SetMessagesUpdateProcessor;
 import org.apache.james.mailbox.cassandra.CassandraId;
@@ -54,6 +55,7 @@ public class MethodsModule extends AbstractModule {
         methods.addBinding().to(new TypeLiteral<GetMessagesMethod<CassandraId>>(){});
         methods.addBinding().to(new TypeLiteral<SetMessagesMethod<CassandraId>>(){});
         bind(SetMessagesUpdateProcessor.class).to(new TypeLiteral<SetMessagesUpdateProcessor<CassandraId>>(){});
+        bind(SetMessagesCreationProcessor.class).to(new TypeLiteral<SetMessagesCreationProcessor<CassandraId>>(){});
     }
 
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
index fcc3101..3acaf8c 100644
--- a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
@@ -648,7 +648,6 @@ public abstract class SetMessagesMethodTest {
     }
 
     @Test
-    @Ignore("pending SetMessages's send messages feature implementation")
     public void setMessageShouldReturnCreatedMessageWhenSendingMessage() {
         String messageCreationId = "user|inbox|1";
         String requestBody = "[" +
@@ -701,7 +700,6 @@ public abstract class SetMessagesMethodTest {
     }
 
     @Test
-    @Ignore("pending SetMessages's send messages feature implementation")
     public void setMessagesShouldCreateMessageInOutboxWhenSendingMessage() throws MailboxException {
         // Given
         String messageCreationId = "user|inbox|1";

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java
new file mode 100644
index 0000000..88692c8
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.jmap.exceptions;
+
+import org.apache.james.jmap.model.mailbox.Role;
+
+public class MailboxRoleNotFoundException extends RuntimeException {
+
+    private final Role role;
+
+    public MailboxRoleNotFoundException(Role role) {
+        super(String.format("Could not find any mailbox with role '%s'", role.serialize()));
+        this.role = role;
+    }
+
+    public Role getRole() {
+        return role;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
new file mode 100644
index 0000000..05d0229
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
@@ -0,0 +1,150 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.Emailer;
+import org.apache.james.mime4j.Charsets;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.FieldParser;
+import org.apache.james.mime4j.dom.Header;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.MessageBuilder;
+import org.apache.james.mime4j.dom.TextBody;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.field.UnstructuredField;
+import org.apache.james.mime4j.field.Fields;
+import org.apache.james.mime4j.field.UnstructuredFieldImpl;
+import org.apache.james.mime4j.io.InputStreams;
+import org.apache.james.mime4j.message.BasicBodyFactory;
+import org.apache.james.mime4j.message.BodyFactory;
+import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.message.DefaultMessageWriter;
+import org.apache.james.mime4j.message.HeaderImpl;
+import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.stream.RawField;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+
+public class MIMEMessageConverter {
+
+    private final MessageBuilder messageBuilder;
+    private final BodyFactory bodyFactory;
+
+    public MIMEMessageConverter() {
+        this.messageBuilder = new DefaultMessageBuilder();
+        this.bodyFactory = new BasicBodyFactory();
+    }
+
+    public byte[] convert(MessageWithId.CreationMessageEntry creationMessageEntry) {
+
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        DefaultMessageWriter writer = new DefaultMessageWriter();
+        try {
+            writer.writeMessage(convertToMime(creationMessageEntry), buffer);
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+        return buffer.toByteArray();
+    }
+
+    @VisibleForTesting Message convertToMime(MessageWithId.CreationMessageEntry creationMessageEntry) {
+        if (creationMessageEntry == null || creationMessageEntry.getMessage() == null) {
+            throw new IllegalArgumentException("creationMessageEntry is either null or has null message");
+        }
+
+        Message message = messageBuilder.newMessage();
+        message.setBody(createTextBody(creationMessageEntry.getMessage()));
+        message.setHeader(buildMimeHeaders(creationMessageEntry.getCreationId(), creationMessageEntry.getMessage()));
+        return message;
+    }
+
+    private Header buildMimeHeaders(String creationId, CreationMessage newMessage) {
+        Header messageHeaders = new HeaderImpl();
+
+        // add From: and Sender: headers
+        newMessage.getFrom().map(this::convertEmailToMimeHeader)
+                .map(Fields::from)
+                .ifPresent(f -> messageHeaders.addField(f));
+        newMessage.getFrom().map(this::convertEmailToMimeHeader)
+                .map(Fields::sender)
+                .ifPresent(f -> messageHeaders.addField(f));
+
+        // add Reply-To:
+        messageHeaders.addField(Fields.replyTo(newMessage.getReplyTo().stream()
+                .map(rt -> convertEmailToMimeHeader(rt))
+                .collect(Collectors.toList())));
+        // add To: headers
+        messageHeaders.addField(Fields.to(newMessage.getTo().stream()
+                .map(this::convertEmailToMimeHeader)
+                .collect(Collectors.toList())));
+        // add Cc: headers
+        messageHeaders.addField(Fields.cc(newMessage.getCc().stream()
+                .map(this::convertEmailToMimeHeader)
+                .collect(Collectors.toList())));
+        // add Bcc: headers
+        messageHeaders.addField(Fields.bcc(newMessage.getBcc().stream()
+                .map(this::convertEmailToMimeHeader)
+                .collect(Collectors.toList())));
+        // add Subject: header
+        messageHeaders.addField(Fields.subject(newMessage.getSubject()));
+        // set creation Id as MessageId: header
+        messageHeaders.addField(Fields.messageId(creationId));
+
+        // date(String fieldName, Date date, TimeZone zone)
+        // note that date conversion probably lose milliseconds !
+        messageHeaders.addField(Fields.date("Date",
+                Date.from(newMessage.getDate().toInstant()), TimeZone.getTimeZone(newMessage.getDate().getZone())
+        ));
+        newMessage.getInReplyToMessageId().ifPresent(addInReplyToHeader(messageHeaders::addField));
+        return messageHeaders;
+    }
+
+    private Consumer<String> addInReplyToHeader(Consumer<Field> headerAppender) {
+        return msgId -> {
+            FieldParser<UnstructuredField> parser = UnstructuredFieldImpl.PARSER;
+            RawField rawField = new RawField("In-Reply-To", msgId);
+            headerAppender.accept(parser.parse(rawField, DecodeMonitor.SILENT));
+        };
+    }
+
+    private TextBody createTextBody(CreationMessage newMessage) {
+        try {
+            return bodyFactory.textBody(
+                    InputStreams.create(newMessage.getTextBody().orElse(""), Charsets.UTF_8),
+                    Charsets.UTF_8.name());
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private Mailbox convertEmailToMimeHeader(Emailer address) {
+        String[] splitAddress = address.getEmail().split("@", 2);
+        return new Mailbox(address.getName(), null, splitAddress[0], splitAddress[1]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
new file mode 100644
index 0000000..fbc7105
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import org.apache.james.jmap.model.CreationMessage;
+
+public class MessageWithId<T> {
+
+    private String creationId;
+    private T message;
+
+    public MessageWithId(String creationId, T message) {
+        this.creationId = creationId;
+        this.message = message;
+    }
+
+    public String getCreationId() {
+        return creationId;
+    }
+
+    public T getMessage() {
+        return message;
+    }
+
+    public static class CreationMessageEntry extends MessageWithId<CreationMessage> {
+        public CreationMessageEntry(String creationId, CreationMessage message) {
+            super(creationId, message);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
new file mode 100644
index 0000000..3cb11db
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.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.jmap.methods;
+
+import java.util.Date;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+import javax.mail.Flags;
+import javax.mail.internet.SharedInputStream;
+import javax.mail.util.SharedByteArrayInputStream;
+
+import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
+import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageId;
+import org.apache.james.jmap.model.SetMessagesRequest;
+import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.jmap.model.mailbox.Role;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxMetaData;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MailboxQuery;
+import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
+import org.apache.james.mailbox.store.mail.MessageMapper;
+import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.fge.lambdas.functions.ThrowingFunction;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+
+public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMessagesProcessor<Id> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesCreationProcessor.class);
+
+    private final MailboxMapperFactory<Id> mailboxMapperFactory;
+    private final MailboxManager mailboxManager;
+    private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
+    private final MIMEMessageConverter mimeMessageConverter;
+
+    @Inject
+    @VisibleForTesting
+    SetMessagesCreationProcessor(MailboxMapperFactory<Id> mailboxMapperFactory,
+                                 MailboxManager mailboxManager,
+                                 MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, MIMEMessageConverter mimeMessageConverter) {
+        this.mailboxMapperFactory = mailboxMapperFactory;
+        this.mailboxManager = mailboxManager;
+        this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
+        this.mimeMessageConverter = mimeMessageConverter;
+    }
+
+    @Override
+    public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) {
+        Mailbox<Id> outbox;
+        try {
+            outbox = getOutbox(mailboxSession).orElseThrow(() -> new MailboxRoleNotFoundException(Role.OUTBOX));
+        } catch (MailboxException | MailboxRoleNotFoundException e) {
+            LOGGER.error("Unable to find a mailbox with role 'outbox'!");
+            throw Throwables.propagate(e);
+        }
+        return request.getCreate().entrySet().stream()
+                .map(e -> new MessageWithId.CreationMessageEntry(e.getKey(), e.getValue()))
+                .map(nuMsg -> createMessageInOutbox(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox)))
+                .map(msg -> SetMessagesResponse.builder().created(ImmutableMap.of(msg.getCreationId(), msg.getMessage())).build())
+                .reduce(SetMessagesResponse.builder(), SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner)
+                .build();
+    }
+
+    @VisibleForTesting
+    protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry,
+                                                           MailboxSession session,
+                                                           Mailbox<Id> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
+        try {
+            MessageMapper<Id> messageMapper = mailboxSessionMapperFactory.createMessageMapper(session);
+            MailboxMessage<Id> newMailboxMessage = buildMailboxMessage(createdEntry, outbox);
+            messageMapper.add(outbox, newMailboxMessage);
+            return new MessageWithId<>(createdEntry.getCreationId(), Message.fromMailboxMessage(newMailboxMessage, buildMessageIdFromUid));
+        } catch (MailboxException e) {
+            throw Throwables.propagate(e);
+        } catch (MailboxRoleNotFoundException e) {
+            LOGGER.error("Could not find mailbox '%s' while trying to save message.", e.getRole().serialize());
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private Function<Long, MessageId> buildMessageIdFunc(MailboxSession session, Mailbox<Id> outbox) {
+        MailboxPath outboxPath = new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), outbox.getName());
+        return uid -> new MessageId(session.getUser(), outboxPath, uid);
+    }
+
+    private MailboxMessage<Id> buildMailboxMessage(MessageWithId.CreationMessageEntry createdEntry, Mailbox<Id> outbox) {
+        byte[] messageContent = mimeMessageConverter.convert(createdEntry);
+        SharedInputStream content = new SharedByteArrayInputStream(messageContent);
+        long size = messageContent.length;
+        int bodyStartOctet = 0;
+
+        Flags flags = getMessageFlags(createdEntry.getMessage());
+        PropertyBuilder propertyBuilder = buildPropertyBuilder();
+        Id mailboxId = outbox.getMailboxId();
+        Date internalDate = Date.from(createdEntry.getMessage().getDate().toInstant());
+
+        return new SimpleMailboxMessage<>(internalDate, size,
+                bodyStartOctet, content, flags, propertyBuilder, mailboxId);
+    }
+
+    @VisibleForTesting
+    protected Optional<Mailbox<Id>> getOutbox(MailboxSession session) throws MailboxException {
+        return mailboxManager.search(MailboxQuery.builder(session)
+                .privateUserMailboxes().build(), session).stream()
+            .map(MailboxMetaData::getPath)
+            .filter(this::hasRoleOutbox)
+            .map(loadMailbox(session))
+            .findFirst();
+    }
+
+    private boolean hasRoleOutbox(MailboxPath mailBoxPath) {
+        return Role.from(mailBoxPath.getName())
+                .map(Role.OUTBOX::equals)
+                .orElse(false);
+    }
+
+    private ThrowingFunction<MailboxPath, Mailbox<Id>> loadMailbox(MailboxSession session) {
+        return path -> mailboxMapperFactory.getMailboxMapper(session).findMailboxByPath(path);
+    }
+
+    private PropertyBuilder buildPropertyBuilder() {
+        return new PropertyBuilder();
+    }
+
+    private Flags getMessageFlags(CreationMessage message) {
+        Flags result = new Flags();
+        if (!message.isIsUnread()) {
+            result.add(Flags.Flag.SEEN);
+        }
+        if (message.isIsFlagged()) {
+            result.add(Flags.Flag.FLAGGED);
+        }
+        if (message.isIsAnswered() || message.getInReplyToMessageId().isPresent()) {
+            result.add(Flags.Flag.ANSWERED);
+        }
+        if (message.isIsDraft()) {
+            result.add(Flags.Flag.DRAFT);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
index f14cdba..5202026 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
@@ -56,13 +56,17 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method {
     private final MailboxMapperFactory<Id> mailboxMapperFactory;
     private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
     private final SetMessagesUpdateProcessor<Id> messageUpdater;
+    private final SetMessagesCreationProcessor messageCreator;
 
     @Inject
     @VisibleForTesting SetMessagesMethod(MailboxMapperFactory<Id> mailboxMapperFactory,
-                                         MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, SetMessagesUpdateProcessor<Id> messageUpdater) {
+                                         MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory,
+                                         SetMessagesUpdateProcessor<Id> messageUpdater,
+                                         SetMessagesCreationProcessor messageCreator) {
         this.mailboxMapperFactory = mailboxMapperFactory;
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
         this.messageUpdater = messageUpdater;
+        this.messageCreator = messageCreator;
     }
 
     @Override
@@ -95,7 +99,8 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method {
     private SetMessagesResponse setMessagesResponse(SetMessagesRequest request, MailboxSession mailboxSession) throws MailboxException {
         SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder();
         processDestroy(request.getDestroy(), mailboxSession, responseBuilder);
-        messageUpdater.processUpdates(request, mailboxSession).mergeInto(responseBuilder);
+        messageUpdater.process(request, mailboxSession).mergeInto(responseBuilder);
+        messageCreator.process(request, mailboxSession).mergeInto(responseBuilder);
         return responseBuilder.build();
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java
new file mode 100644
index 0000000..4c7e00d
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import org.apache.james.jmap.model.SetMessagesRequest;
+import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+
+public interface SetMessagesProcessor<Id extends MailboxId> {
+    SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java
index f62e390..ed7e219 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java
@@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class SetMessagesUpdateProcessor<Id extends MailboxId> {
+public class SetMessagesUpdateProcessor<Id extends MailboxId> implements SetMessagesProcessor<Id> {
 
     private static final int LIMIT_BY_ONE = 1;
     private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesUpdateProcessor.class);
@@ -71,7 +71,7 @@ public class SetMessagesUpdateProcessor<Id extends MailboxId> {
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
     }
 
-    public SetMessagesResponse processUpdates(SetMessagesRequest request,  MailboxSession mailboxSession) {
+    public SetMessagesResponse process(SetMessagesRequest request,  MailboxSession mailboxSession) {
         SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder();
         request.buildUpdatePatches(updatePatchConverter).forEach( (id, patch) -> {
             if (patch.isValid()) {

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
new file mode 100644
index 0000000..697c9c7
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
@@ -0,0 +1,305 @@
+/****************************************************************
+ * 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.jmap.model;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.james.jmap.methods.GetMessagesMethod;
+import org.apache.james.jmap.methods.JmapResponseWriterImpl;
+
+import com.fasterxml.jackson.annotation.JsonFilter;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@JsonDeserialize(builder = CreationMessage.Builder.class)
+public class CreationMessage {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonPOJOBuilder(withPrefix = "")
+    public static class Builder {
+        private ImmutableList<String> mailboxIds;
+        private String inReplyToMessageId;
+        private boolean isUnread;
+        private boolean isFlagged;
+        private boolean isAnswered;
+        private boolean isDraft;
+        private final ImmutableMap.Builder<String, String> headers;
+        private Emailer from;
+        private final ImmutableList.Builder<Emailer> to;
+        private final ImmutableList.Builder<Emailer> cc;
+        private final ImmutableList.Builder<Emailer> bcc;
+        private final ImmutableList.Builder<Emailer> replyTo;
+        private String subject;
+        private ZonedDateTime date;
+        private String textBody;
+        private String htmlBody;
+        private final ImmutableList.Builder<Attachment> attachments;
+        private final ImmutableMap.Builder<String, SubMessage> attachedMessages;
+
+        private Builder() {
+            to = ImmutableList.builder();
+            cc = ImmutableList.builder();
+            bcc = ImmutableList.builder();
+            replyTo = ImmutableList.builder();
+            attachments = ImmutableList.builder();
+            attachedMessages = ImmutableMap.builder();
+            headers = ImmutableMap.builder();
+        }
+
+        public Builder mailboxIds(ImmutableList<String> mailboxIds) {
+            this.mailboxIds = mailboxIds;
+            return this;
+        }
+
+        public Builder inReplyToMessageId(String inReplyToMessageId) {
+            this.inReplyToMessageId = inReplyToMessageId;
+            return this;
+        }
+
+        public Builder isUnread(boolean isUnread) {
+            this.isUnread = isUnread;
+            return this;
+        }
+
+        public Builder isFlagged(boolean isFlagged) {
+            this.isFlagged = isFlagged;
+            return this;
+        }
+
+        public Builder isAnswered(boolean isAnswered) {
+            this.isAnswered = isAnswered;
+            return this;
+        }
+
+        public Builder isDraft(boolean isDraft) {
+            this.isDraft = isDraft;
+            return this;
+        }
+
+        public Builder headers(ImmutableMap<String, String> headers) {
+            this.headers.putAll(headers);
+            return this;
+        }
+
+        public Builder from(Emailer from) {
+            this.from = from;
+            return this;
+        }
+
+        public Builder to(List<Emailer> to) {
+            this.to.addAll(to);
+            return this;
+        }
+
+        public Builder cc(List<Emailer> cc) {
+            this.cc.addAll(cc);
+            return this;
+        }
+
+        public Builder bcc(List<Emailer> bcc) {
+            this.bcc.addAll(bcc);
+            return this;
+        }
+
+        public Builder replyTo(List<Emailer> replyTo) {
+            this.replyTo.addAll(replyTo);
+            return this;
+        }
+
+        public Builder subject(String subject) {
+            this.subject = subject;
+            return this;
+        }
+
+        public Builder date(ZonedDateTime date) {
+            this.date = date;
+            return this;
+        }
+
+        public Builder textBody(String textBody) {
+            this.textBody = textBody;
+            return this;
+        }
+
+        public Builder htmlBody(String htmlBody) {
+            this.htmlBody = htmlBody;
+            return this;
+        }
+
+        public Builder attachments(List<Attachment> attachments) {
+            this.attachments.addAll(attachments);
+            return this;
+        }
+
+        public Builder attachedMessages(Map<String, SubMessage> attachedMessages) {
+            this.attachedMessages.putAll(attachedMessages);
+            return this;
+        }
+
+        private static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) {
+            return attachments.stream()
+                    .map(Attachment::getBlobId)
+                    .allMatch(attachedMessages::containsKey);
+        }
+
+        public CreationMessage build() {
+            Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory");
+            Preconditions.checkState(headers != null, "'headers' is mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(subject), "'subject' is mandatory");
+            ImmutableList<Attachment> attachments = this.attachments.build();
+            ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
+            Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachments'");
+
+            if (date == null) {
+                date = ZonedDateTime.now();
+            }
+
+            return new CreationMessage(mailboxIds, Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, isDraft, headers.build(), Optional.ofNullable(from),
+                    to.build(), cc.build(), bcc.build(), replyTo.build(), subject, date, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody), attachments, attachedMessages);
+        }
+    }
+
+    private final ImmutableList<String> mailboxIds;
+    private final Optional<String> inReplyToMessageId;
+    private final boolean isUnread;
+    private final boolean isFlagged;
+    private final boolean isAnswered;
+    private final boolean isDraft;
+    private final ImmutableMap<String, String> headers;
+    private final Optional<Emailer> from;
+    private final ImmutableList<Emailer> to;
+    private final ImmutableList<Emailer> cc;
+    private final ImmutableList<Emailer> bcc;
+    private final ImmutableList<Emailer> replyTo;
+    private final String subject;
+    private final ZonedDateTime date;
+    private final Optional<String> textBody;
+    private final Optional<String> htmlBody;
+    private final ImmutableList<Attachment> attachments;
+    private final ImmutableMap<String, SubMessage> attachedMessages;
+
+    @VisibleForTesting
+    CreationMessage(ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, ImmutableMap<String, String> headers, Optional<Emailer> from,
+                    ImmutableList<Emailer> to, ImmutableList<Emailer> cc, ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments,
+                    ImmutableMap<String, SubMessage> attachedMessages) {
+        this.mailboxIds = mailboxIds;
+        this.inReplyToMessageId = inReplyToMessageId;
+        this.isUnread = isUnread;
+        this.isFlagged = isFlagged;
+        this.isAnswered = isAnswered;
+        this.isDraft = isDraft;
+        this.headers = headers;
+        this.from = from;
+        this.to = to;
+        this.cc = cc;
+        this.bcc = bcc;
+        this.replyTo = replyTo;
+        this.subject = subject;
+        this.date = date;
+        this.textBody = textBody;
+        this.htmlBody = htmlBody;
+        this.attachments = attachments;
+        this.attachedMessages = attachedMessages;
+    }
+
+    public ImmutableList<String> getMailboxIds() {
+        return mailboxIds;
+    }
+
+    public Optional<String> getInReplyToMessageId() {
+        return inReplyToMessageId;
+    }
+
+    public boolean isIsUnread() {
+        return isUnread;
+    }
+
+    public boolean isIsFlagged() {
+        return isFlagged;
+    }
+
+    public boolean isIsAnswered() {
+        return isAnswered;
+    }
+
+    public boolean isIsDraft() {
+        return isDraft;
+    }
+
+    public ImmutableMap<String, String> getHeaders() {
+        return headers;
+    }
+
+    public Optional<Emailer> getFrom() {
+        return from;
+    }
+
+    public ImmutableList<Emailer> getTo() {
+        return to;
+    }
+
+    public ImmutableList<Emailer> getCc() {
+        return cc;
+    }
+
+    public ImmutableList<Emailer> getBcc() {
+        return bcc;
+    }
+
+    public ImmutableList<Emailer> getReplyTo() {
+        return replyTo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public ZonedDateTime getDate() {
+        return date;
+    }
+
+    public Optional<String> getTextBody() {
+        return textBody;
+    }
+
+    public Optional<String> getHtmlBody() {
+        return htmlBody;
+    }
+
+    public ImmutableList<Attachment> getAttachments() {
+        return attachments;
+    }
+
+    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+        return attachedMessages;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
index aadb886..8e25ac5 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
@@ -48,7 +48,7 @@ public class SetMessagesRequest implements JmapRequest {
 
         private String accountId;
         private String ifInState;
-        private ImmutableMap.Builder<MessageId, Message> create;
+        private ImmutableMap.Builder<String, CreationMessage> create;
         private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider;
 
         private ImmutableList.Builder<MessageId> destroy;
@@ -73,8 +73,8 @@ public class SetMessagesRequest implements JmapRequest {
             return this;
         }
 
-        public Builder create(Map<MessageId, Message> creates) {
-            this.create.putAll(creates);
+        public Builder create(Map<String, CreationMessage> creations) {
+            this.create.putAll(creations);
             return this;
         }
 
@@ -95,11 +95,11 @@ public class SetMessagesRequest implements JmapRequest {
 
     private final Optional<String> accountId;
     private final Optional<String> ifInState;
-    private final Map<MessageId, Message> create;
+    private final Map<String, CreationMessage> create;
     private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update;
     private final List<MessageId> destroy;
 
-    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<MessageId, Message> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
+    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<String, CreationMessage> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
         this.accountId = accountId;
         this.ifInState = ifInState;
         this.create = create;
@@ -115,7 +115,7 @@ public class SetMessagesRequest implements JmapRequest {
         return ifInState;
     }
 
-    public Map<MessageId, Message> getCreate() {
+    public Map<String, CreationMessage> getCreate() {
         return create;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
index 304f8c3..c096c8a 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java
@@ -20,9 +20,11 @@ package org.apache.james.jmap.model;
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
 
 import com.google.common.base.Strings;
 import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.jmap.methods.MessageWithId;
 import org.apache.james.jmap.methods.Method;
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -42,10 +44,18 @@ public class SetMessagesResponse implements Method.Response {
     @JsonPOJOBuilder(withPrefix = "")
     public static class Builder {
 
+        public static Builder accumulator(Builder accumulator, SetMessagesResponse response) {
+            return response.mergeInto(accumulator);
+        }
+
+        public static Builder combiner(Builder firstBuilder, Builder secondBuilder) {
+            return secondBuilder.build().mergeInto(firstBuilder);
+        }
+
         private String accountId;
         private String oldState;
         private String newState;
-        private ImmutableList.Builder<Message> created;
+        private ImmutableMap.Builder<String, Message> created;
         private ImmutableList.Builder<MessageId> updated;
         private ImmutableList.Builder<MessageId> destroyed;
         private ImmutableMap.Builder<MessageId, SetError> notCreated;
@@ -53,7 +63,7 @@ public class SetMessagesResponse implements Method.Response {
         private ImmutableMap.Builder<MessageId, SetError> notDestroyed;
 
         private Builder() {
-            created = ImmutableList.builder();
+            created = ImmutableMap.builder();
             updated = ImmutableList.builder();
             destroyed = ImmutableList.builder();
             notCreated = ImmutableMap.builder();
@@ -73,8 +83,8 @@ public class SetMessagesResponse implements Method.Response {
             throw new NotImplementedException();
         }
 
-        public Builder created(List<Message> created) {
-            this.created.addAll(created);
+        public Builder created(Map<String, Message> created) {
+            this.created.putAll(created);
             return this;
         }
 
@@ -122,14 +132,14 @@ public class SetMessagesResponse implements Method.Response {
     private final String accountId;
     private final String oldState;
     private final String newState;
-    private final List<Message> created;
+    private final Map<String, Message> created;
     private final List<MessageId> updated;
     private final List<MessageId> destroyed;
     private final Map<MessageId, SetError> notCreated;
     private final Map<MessageId, SetError> notUpdated;
     private final Map<MessageId, SetError> notDestroyed;
 
-    @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, List<Message> created, List<MessageId> updated, List<MessageId> destroyed, 
+    @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, Map<String, Message> created, List<MessageId> updated, List<MessageId> destroyed,
             Map<MessageId, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) {
         this.accountId = accountId;
         this.oldState = oldState;
@@ -158,7 +168,7 @@ public class SetMessagesResponse implements Method.Response {
     }
 
     @JsonSerialize
-    public List<Message> getCreated() {
+    public Map<String, Message> getCreated() {
         return created;
     }
 
@@ -187,7 +197,7 @@ public class SetMessagesResponse implements Method.Response {
         return notDestroyed;
     }
 
-    public void mergeInto(SetMessagesResponse.Builder responseBuilder) {
+    public SetMessagesResponse.Builder mergeInto(SetMessagesResponse.Builder responseBuilder) {
         responseBuilder.created(getCreated());
         responseBuilder.updated(getUpdated());
         responseBuilder.destroyed(getDestroyed());
@@ -203,5 +213,6 @@ public class SetMessagesResponse implements Method.Response {
         if(! Strings.isNullOrEmpty(getNewState())) {
             responseBuilder.accountId(getAccountId());
         }
+        return responseBuilder;
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
new file mode 100644
index 0000000..0b73ee2
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -0,0 +1,131 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.Emailer;
+import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageId;
+import org.apache.james.jmap.model.SetMessagesRequest;
+import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.mailbox.store.mail.MessageMapper;
+import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class SetMessagesCreationProcessorTest {
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() {
+        SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, null, null) {
+            @Override
+            protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException {
+                Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class);
+                when(fakeOutbox.getName()).thenReturn("outbox");
+                return Optional.of(fakeOutbox);
+            }
+        };
+        SetMessagesRequest requestWithEmptyCreate = SetMessagesRequest.builder().build();
+
+        SetMessagesResponse result = sut.process(requestWithEmptyCreate, buildStubbedSession());
+
+        assertThat(result.getCreated()).isEmpty();
+        assertThat(result.getNotCreated()).isEmpty();
+    }
+
+    private MailboxSession buildStubbedSession() {
+        MailboxSession.User stubUser = mock(MailboxSession.User.class);
+        when(stubUser.getUserName()).thenReturn("user");
+        MailboxSession stubSession = mock(MailboxSession.class);
+        when(stubSession.getPathDelimiter()).thenReturn('.');
+        when(stubSession.getUser()).thenReturn(stubUser);
+        when(stubSession.getPersonalSpace()).thenReturn("#private");
+        return stubSession;
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void processShouldReturnNonEmptyCreatedWhenRequestHasNonEmptyCreate() throws MailboxException {
+
+        MessageMapper<MailboxId> stubMapper = mock(MessageMapper.class);
+        MailboxSessionMapperFactory<MailboxId> mockSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
+        when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
+                .thenReturn(stubMapper);
+
+        SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, mockSessionMapperFactory, null) {
+            @Override
+            protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<MailboxId> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
+                return new MessageWithId<>(createdEntry.creationId, getFakeMessage());
+            }
+            @Override
+            protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException {
+                Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class);
+                when(fakeOutbox.getName()).thenReturn("outbox");
+                return Optional.of(fakeOutbox);
+            }
+        };
+
+        SetMessagesRequest creationRequest = SetMessagesRequest.builder()
+                .create(ImmutableMap.of("anything-really", CreationMessage.builder()
+                    .from(Emailer.builder().name("alice").email("alice@example.com").build())
+                    .to(ImmutableList.of(Emailer.builder().name("bob").email("bob@example.com").build()))
+                    .subject("Hey! ")
+                    .mailboxIds(ImmutableList.of("mailboxId"))
+                    .build()
+                ))
+                .build()
+                ;
+
+        SetMessagesResponse result = sut.process(creationRequest, buildStubbedSession());
+
+        assertThat(result.getCreated()).isNotEmpty();
+        assertThat(result.getNotCreated()).isEmpty();
+    }
+
+    private Message getFakeMessage() {
+        return Message.builder()
+                .id(org.apache.james.jmap.model.MessageId.of("user|outbox|1"))
+                .blobId("anything")
+                .threadId("anything")
+                .mailboxIds(ImmutableList.of("mailboxId"))
+                .headers(ImmutableMap.of())
+                .subject("anything")
+                .size(0)
+                .date(ZonedDateTime.now())
+                .preview("anything")
+                .build();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
index e2bc7c7..9877651 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
@@ -55,7 +55,7 @@ public class SetMessagesResponseTest {
     @Test
     public void builderShouldWork() {
         ZonedDateTime currentDate = ZonedDateTime.now();
-        ImmutableList<Message> created = ImmutableList.of(
+        ImmutableMap<String, Message> created = ImmutableMap.of("user|created|1",
             Message.builder()
                 .id(MessageId.of("user|created|1"))
                 .blobId("blobId")
@@ -105,8 +105,8 @@ public class SetMessagesResponseTest {
         assertThat(emptyBuilder.build()).isEqualToComparingFieldByField(testee);
     }
 
-    private ImmutableList<Message> buildMessage(String messageId) {
-        return ImmutableList.of(Message.builder()
+    private ImmutableMap<String, Message> buildMessage(String messageId) {
+        return ImmutableMap.of(messageId, Message.builder()
                 .id(MessageId.of(messageId))
                 .blobId("blobId")
                 .threadId("threadId")
@@ -152,10 +152,7 @@ public class SetMessagesResponseTest {
         SetMessagesResponse mergedResponse = nonEmptyBuilder.build();
 
         // Then
-        List<String> createdMessageIds = mergedResponse.getCreated().stream()
-                .map(m -> m.getId().serialize())
-                .collect(Collectors.toList());
-        assertThat(createdMessageIds).containsExactly(buildersCreatedMessageId, createdMessageId);
+        assertThat(mergedResponse.getCreated().keySet()).containsExactly(buildersCreatedMessageId, createdMessageId);
     }
 
     @Test


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


[9/9] james-project git commit: JAMES-1692 Send the message throw the MailSpool

Posted by ad...@apache.org.
JAMES-1692 Send the message throw the MailSpool


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

Branch: refs/heads/master
Commit: 3b09ad7534457312572a65d1bd6e61956d8b85ec
Parents: 8174cb7
Author: Raphael Ouazana <ra...@linagora.com>
Authored: Fri Feb 26 09:28:44 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:46:32 2016 +0100

----------------------------------------------------------------------
 .../org/apache/james/jmap/JMAPCommonModule.java |   2 +
 .../methods/SetMessagesCreationProcessor.java   |  33 ++++--
 .../org/apache/james/jmap/send/MailFactory.java |  78 +++++++++++++++
 .../SetMessagesCreationProcessorTest.java       |  48 +++++++--
 .../apache/james/jmap/send/MailFactoryTest.java | 100 +++++++++++++++++++
 5 files changed, 248 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/3b09ad75/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
----------------------------------------------------------------------
diff --git a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/JMAPCommonModule.java b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
index 14c2d9f..d2ad4b0 100644
--- a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
+++ b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
@@ -29,6 +29,7 @@ import org.apache.james.jmap.crypto.JamesSignatureHandler;
 import org.apache.james.jmap.crypto.SignatureHandler;
 import org.apache.james.jmap.crypto.SignedContinuationTokenManager;
 import org.apache.james.jmap.memory.access.MemoryAccessTokenRepository;
+import org.apache.james.jmap.send.MailFactory;
 import org.apache.james.jmap.send.MailSpool;
 import org.apache.james.jmap.utils.DefaultZonedDateTimeProvider;
 import org.apache.james.jmap.utils.ZonedDateTimeProvider;
@@ -54,6 +55,7 @@ public class JMAPCommonModule extends AbstractModule {
         bind(AccessTokenManager.class).to(AccessTokenManagerImpl.class);
 
         bind(MailSpool.class).in(Singleton.class);
+        bind(MailFactory.class).in(Singleton.class);
     }
 
     @Provides

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b09ad75/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
index 718734d..da871a2 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java
@@ -19,8 +19,7 @@
 
 package org.apache.james.jmap.methods;
 
-import static org.apache.james.jmap.model.MessageProperties.MessageProperty;
-
+import java.io.IOException;
 import java.util.AbstractMap;
 import java.util.Date;
 import java.util.List;
@@ -33,6 +32,7 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.mail.Flags;
+import javax.mail.MessagingException;
 import javax.mail.internet.SharedInputStream;
 import javax.mail.util.SharedByteArrayInputStream;
 
@@ -42,10 +42,14 @@ import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessageProperties;
+import org.apache.james.jmap.model.MessageProperties.MessageProperty;
 import org.apache.james.jmap.model.SetError;
 import org.apache.james.jmap.model.SetMessagesRequest;
 import org.apache.james.jmap.model.SetMessagesResponse;
 import org.apache.james.jmap.model.mailbox.Role;
+import org.apache.james.jmap.send.MailFactory;
+import org.apache.james.jmap.send.MailMetadata;
+import org.apache.james.jmap.send.MailSpool;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
@@ -60,6 +64,7 @@ import org.apache.james.mailbox.store.mail.model.MailboxId;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.apache.mailet.Mail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -77,17 +82,23 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
     private final MailboxManager mailboxManager;
     private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
     private final MIMEMessageConverter mimeMessageConverter;
+    private final MailSpool mailSpool;
+    private final MailFactory<Id> mailFactory;
 
     @Inject
     @VisibleForTesting
     SetMessagesCreationProcessor(MailboxMapperFactory<Id> mailboxMapperFactory,
                                  MailboxManager mailboxManager,
                                  MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory,
-                                 MIMEMessageConverter mimeMessageConverter) {
+                                 MIMEMessageConverter mimeMessageConverter,
+                                 MailSpool mailSpool,
+                                 MailFactory<Id> mailFactory) {
         this.mailboxMapperFactory = mailboxMapperFactory;
         this.mailboxManager = mailboxManager;
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
         this.mimeMessageConverter = mimeMessageConverter;
+        this.mailSpool = mailSpool;
+        this.mailFactory = mailFactory;
     }
 
     @Override
@@ -109,7 +120,7 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
         return request.getCreate().entrySet().stream()
                 .filter(e -> validMessagesTester.test(e.getValue()))
                 .map(e -> new MessageWithId.CreationMessageEntry(e.getKey(), e.getValue()))
-                .map(nuMsg -> createMessageInOutbox(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox)))
+                .map(nuMsg -> createMessageInOutboxAndSend(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox)))
                 .map(msg -> SetMessagesResponse.builder().created(ImmutableMap.of(msg.getCreationId(), msg.getMessage())).build())
                 .reduce(responseBuilder, SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner)
                 .build();
@@ -140,15 +151,17 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
     }
 
     @VisibleForTesting
-    protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry,
+    protected MessageWithId<Message> createMessageInOutboxAndSend(MessageWithId.CreationMessageEntry createdEntry,
                                                            MailboxSession session,
                                                            Mailbox<Id> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
         try {
             MessageMapper<Id> messageMapper = mailboxSessionMapperFactory.createMessageMapper(session);
             MailboxMessage<Id> newMailboxMessage = buildMailboxMessage(createdEntry, outbox);
             messageMapper.add(outbox, newMailboxMessage);
-            return new MessageWithId<>(createdEntry.getCreationId(), Message.fromMailboxMessage(newMailboxMessage, buildMessageIdFromUid));
-        } catch (MailboxException e) {
+            Message jmapMessage = Message.fromMailboxMessage(newMailboxMessage, buildMessageIdFromUid);
+            sendMessage(newMailboxMessage, jmapMessage, session);
+            return new MessageWithId<>(createdEntry.getCreationId(), jmapMessage);
+        } catch (MailboxException | MessagingException | IOException e) {
             throw Throwables.propagate(e);
         } catch (MailboxRoleNotFoundException e) {
             LOGGER.error("Could not find mailbox '%s' while trying to save message.", e.getRole().serialize());
@@ -216,4 +229,10 @@ public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMe
         }
         return result;
     }
+
+    private void sendMessage(MailboxMessage<Id> mailboxMessage, Message jmapMessage, MailboxSession session) throws MessagingException, IOException {
+        Mail mail = mailFactory.build(mailboxMessage, jmapMessage);
+        MailMetadata metadata = new MailMetadata(jmapMessage.getId(), session.getUser().getUserName());
+        mailSpool.send(mail, metadata);
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b09ad75/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java
new file mode 100644
index 0000000..4de9c1c
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.jmap.send;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+
+import org.apache.james.core.MailImpl;
+import org.apache.james.jmap.model.Emailer;
+import org.apache.james.jmap.model.Message;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+public class MailFactory<Id extends MailboxId> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MailFactory.class);
+    
+    @VisibleForTesting MailFactory() {
+    }
+
+    public Mail build(MailboxMessage<Id> mailboxMessage, Message jmapMessage) throws MessagingException, IOException {
+        MailAddress sender = jmapMessage.getFrom()
+                .map(this::emailerToMailAddress)
+                .orElseThrow(() -> new RuntimeException("Sender is mandatory"));
+        Set<MailAddress> to = emailersToMailAddressSet(jmapMessage.getTo());
+        Set<MailAddress> cc = emailersToMailAddressSet(jmapMessage.getCc());
+        Set<MailAddress> bcc = emailersToMailAddressSet(jmapMessage.getBcc());
+        ImmutableSet<MailAddress> recipients = Sets.union(
+                Sets.union(to, cc),
+                bcc).immutableCopy();
+        return new MailImpl(jmapMessage.getId().serialize(), sender, recipients, mailboxMessage.getBodyContent());
+    }
+
+    private MailAddress emailerToMailAddress(Emailer emailer) {
+        try {
+            return new MailAddress(emailer.getEmail());
+        } catch (AddressException e) {
+            LOGGER.error("Invalid mail address", emailer.getEmail());
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private Set<MailAddress> emailersToMailAddressSet(List<Emailer> emailers) {
+        return emailers.stream()
+            .map(this::emailerToMailAddress)
+            .collect(Collectors.toSet());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b09ad75/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
index 5978f0e..4c73ca8 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.jmap.methods;
 
-import static org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -33,11 +32,15 @@ import java.util.function.Function;
 
 import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException;
 import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.SetMessagesRequest;
 import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.jmap.send.MailFactory;
+import org.apache.james.jmap.send.MailMetadata;
+import org.apache.james.jmap.send.MailSpool;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
@@ -45,6 +48,7 @@ import org.apache.james.mailbox.store.TestId;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.mailet.Mail;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -66,7 +70,7 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() {
-        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null) {
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null, null, null) {
             @Override
             protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
                 @SuppressWarnings("unchecked")
@@ -102,9 +106,9 @@ public class SetMessagesCreationProcessorTest {
         when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
                 .thenReturn(stubMapper);
 
-        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, mockSessionMapperFactory, null) {
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, mockSessionMapperFactory, null, null, null) {
             @Override
-            protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<TestId> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
+            protected MessageWithId<Message> createMessageInOutboxAndSend(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<TestId> outbox, Function<Long, MessageId> buildMessageIdFromUid) {
                 return new MessageWithId<>(createdEntry.getCreationId(), FAKE_MESSAGE);
             }
             @Override
@@ -125,7 +129,7 @@ public class SetMessagesCreationProcessorTest {
     @Test(expected = MailboxRoleNotFoundException.class)
     public void processShouldThrowWhenOutboxNotFound() {
         // Given
-        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null) {
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null, null, null, null, null) {
             @Override
             protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
                 return Optional.empty();
@@ -144,9 +148,11 @@ public class SetMessagesCreationProcessorTest {
         MailboxSessionMapperFactory<TestId> stubSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
         when(stubSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
                 .thenReturn(mockMapper);
+        MailSpool mockedMailSpool = mock(MailSpool.class);
+        MailFactory<TestId> mockedMailFactory = mock(MailFactory.class);
 
         SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null,
-                stubSessionMapperFactory, new MIMEMessageConverter()) {
+                stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory) {
             @Override
             protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
                 TestId stubMailboxId = mock(TestId.class);
@@ -163,6 +169,36 @@ public class SetMessagesCreationProcessorTest {
         verify(mockMapper).add(eq(fakeOutbox), any(MailboxMessage.class));
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void processShouldSendMailWhenRequestHasNonEmptyCreate() throws Exception {
+        // Given
+        Mailbox<TestId> fakeOutbox = mock(Mailbox.class);
+        MessageMapper<TestId> mockMapper = mock(MessageMapper.class);
+        MailboxSessionMapperFactory<TestId> stubSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
+        when(stubSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
+                .thenReturn(mockMapper);
+        MailSpool mockedMailSpool = mock(MailSpool.class);
+        MailFactory<TestId> mockedMailFactory = mock(MailFactory.class);
+
+        SetMessagesCreationProcessor<TestId> sut = new SetMessagesCreationProcessor<TestId>(null, null,
+                stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory) {
+            @Override
+            protected Optional<Mailbox<TestId>> getOutbox(MailboxSession session) throws MailboxException {
+                TestId stubMailboxId = mock(TestId.class);
+                when(stubMailboxId.serialize()).thenReturn("user|outbox|12345");
+                when(fakeOutbox.getMailboxId()).thenReturn(stubMailboxId);
+                when(fakeOutbox.getName()).thenReturn("outbox");
+                return Optional.of(fakeOutbox);
+            }
+        };
+        // When
+        sut.process(buildFakeCreationRequest(), buildStubbedSession());
+
+        // Then
+        verify(mockedMailSpool).send(any(Mail.class), any(MailMetadata.class));
+    }
+
     private SetMessagesRequest buildFakeCreationRequest() {
         return SetMessagesRequest.builder()
                 .create(ImmutableMap.of(CreationMessageId.of("anything-really"), CreationMessage.builder()

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b09ad75/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
new file mode 100644
index 0000000..59f09e2
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
@@ -0,0 +1,100 @@
+/****************************************************************
+ * 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.jmap.send;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+import java.util.Date;
+
+import javax.mail.Flags;
+import javax.mail.util.SharedByteArrayInputStream;
+
+import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageId;
+import org.apache.james.mailbox.FlagsBuilder;
+import org.apache.james.mailbox.store.TestId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+public class MailFactoryTest {
+
+    private MailFactory<TestId> testee;
+    private MailboxMessage<TestId> mailboxMessage;
+    private Message jmapMessage;
+
+    @Before
+    public void init() {
+        testee = new MailFactory<TestId>();
+        String headers = "From: me@example.com\n"
+                + "To: 1@example.com\n"
+                + "Cc: 2@example.com, 3@example.com\n"
+                + "Bcc: 4@example.com\n"
+                + "Subject: news\n";
+        String content = headers
+                + "Hello! How are you?";
+        PropertyBuilder propertyBuilder = new PropertyBuilder();
+        propertyBuilder.setMediaType("plain");
+        propertyBuilder.setSubType("text");
+        propertyBuilder.setTextualLineCount(18L);
+        mailboxMessage = new SimpleMailboxMessage<>(
+                new Date(),
+                content.length(),
+                headers.length(),
+                new SharedByteArrayInputStream(content.getBytes()),
+                new FlagsBuilder().add(Flags.Flag.SEEN).build(),
+                propertyBuilder,
+                TestId.of(2));
+        jmapMessage = Message.fromMailboxMessage(mailboxMessage, x -> MessageId.of("test|test|" + x));
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void buildMailShouldThrowWhenNullMailboxMessage() throws Exception {
+        testee.build(null, jmapMessage);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void buildMailShouldThrowWhenNullJmapMessage() throws Exception {
+        testee.build(mailboxMessage, null);
+    }
+
+    @Test
+    public void buildMailShouldWork() throws Exception {
+        String expectedName = jmapMessage.getId().serialize();
+        MailAddress expectedSender = new MailAddress("me@example.com");
+        Collection<MailAddress> expectedRecipients = ImmutableSet.of(
+                new MailAddress("1@example.com"),
+                new MailAddress("3@example.com"),
+                new MailAddress("2@example.com"),
+                new MailAddress("4@example.com"));
+        
+        Mail actual = testee.build(mailboxMessage, jmapMessage);
+        
+        assertThat(actual.getName()).isEqualTo(expectedName);
+        assertThat(actual.getSender()).isEqualTo(expectedSender);
+        assertThat(actual.getRecipients()).containsAll(expectedRecipients);
+    }
+}


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


[5/9] james-project git commit: JAMES-1692 add a failing test for sending a message

Posted by ad...@apache.org.
JAMES-1692 add a failing test for sending a message


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

Branch: refs/heads/master
Commit: 7c7f377362e6d06f2084e4535af7e2651f252afb
Parents: 00678d8
Author: Fabien Vignon <fv...@linagora.com>
Authored: Fri Feb 12 17:52:43 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:42:53 2016 +0100

----------------------------------------------------------------------
 .../jmap/methods/SetMessagesMethodTest.java     | 135 ++++++++++++++++++-
 1 file changed, 128 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/7c7f3773/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
index c2b4b04..fcc3101 100644
--- a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java
@@ -36,6 +36,9 @@ import static org.hamcrest.collection.IsMapWithSize.anEmptyMap;
 
 import java.io.ByteArrayInputStream;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 import javax.mail.Flags;
 
 import org.apache.james.backends.cassandra.EmbeddedCassandra;
@@ -46,20 +49,19 @@ import org.apache.james.mailbox.elasticsearch.EmbeddedElasticSearch;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxPath;
-
+import org.hamcrest.Matchers;
+import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
 
 import com.google.common.base.Charsets;
 import com.jayway.restassured.RestAssured;
 import com.jayway.restassured.builder.ResponseSpecBuilder;
 import com.jayway.restassured.http.ContentType;
 import com.jayway.restassured.specification.ResponseSpecification;
-import org.hamcrest.Matchers;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TemporaryFolder;
 
 public abstract class SetMessagesMethodTest {
 
@@ -70,6 +72,7 @@ public abstract class SetMessagesMethodTest {
     private EmbeddedElasticSearch embeddedElasticSearch = new EmbeddedElasticSearch();
     private EmbeddedCassandra cassandra = EmbeddedCassandra.createStartServer();
     private JmapServer jmapServer = jmapServer(temporaryFolder, embeddedElasticSearch, cassandra);
+    private String outboxId;
 
     protected abstract JmapServer jmapServer(TemporaryFolder temporaryFolder, EmbeddedElasticSearch embeddedElasticSearch, EmbeddedCassandra cassandra);
 
@@ -94,6 +97,25 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().addUser(username, password);
         jmapServer.serverProbe().createMailbox("#private", "username", "inbox");
         accessToken = JmapAuthentication.authenticateJamesUser(username, password);
+
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "outbox");
+        embeddedElasticSearch.awaitForElasticSearch();
+        // Find the newly created outbox mailbox (using getMailboxes command on /jmap endpoint)
+        List<Map<String, String>> mailboxes = with()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body("[[\"getMailboxes\", {\"properties\": [\"role\", \"id\"]}, \"#0\"]]")
+                .post("/jmap")
+                .andReturn()
+                .body()
+                .jsonPath()
+                .getList(ARGUMENTS + ".list");
+
+        outboxId = mailboxes.stream()
+                .filter(x -> x.get("role").equals("outbox"))
+                .map(x -> x.get("id"))
+                .findFirst().get();
     }
 
     @Test
@@ -624,4 +646,103 @@ public abstract class SetMessagesMethodTest {
             .body(ARGUMENTS + ".notUpdated[\""+nonExistingMessageId+"\"].description", equalTo("message not found"))
             .body(ARGUMENTS + ".updated", hasSize(0));
     }
+
+    @Test
+    @Ignore("pending SetMessages's send messages feature implementation")
+    public void setMessageShouldReturnCreatedMessageWhenSendingMessage() {
+        String messageCreationId = "user|inbox|1";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"MAILER-DEAMON\", \"email\": \"postmaster@example.com\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+        .when()
+                .post("/jmap")
+        .then()
+                .log().ifValidationFails()
+                .statusCode(200)
+                .body(NAME, equalTo("messagesSet"))
+                .body(ARGUMENTS + ".notCreated", aMapWithSize(0))
+                // note that assertions on result message had to be split between
+                // string-typed values and boolean-typed value assertions on the same .created entry
+                // make sure only one creation has been processed
+                .body(ARGUMENTS + ".created", aMapWithSize(1))
+                // assert server-set attributes are returned
+                .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
+                        hasEntry(equalTo("id"), not(isEmptyOrNullString())),
+                        hasEntry(equalTo("blobId"), not(isEmptyOrNullString())),
+                        hasEntry(equalTo("threadId"), not(isEmptyOrNullString())),
+                        hasEntry(equalTo("size"), not(isEmptyOrNullString()))
+                )))
+                // assert that message flags are all unset
+                .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
+                        hasEntry(equalTo("isDraft"), equalTo(false)),
+                        hasEntry(equalTo("isUnread"), equalTo(false)),
+                        hasEntry(equalTo("isFlagged"), equalTo(false)),
+                        hasEntry(equalTo("isAnswered"), equalTo(false))
+                )))
+                ;
+    }
+
+    @Test
+    @Ignore("pending SetMessages's send messages feature implementation")
+    public void setMessagesShouldCreateMessageInOutboxWhenSendingMessage() throws MailboxException {
+        // Given
+        String messageCreationId = "user|inbox|1";
+        String presumedMessageId = "username@domain.tld|outbox|1";
+        String messageSubject = "Thank you for joining example.com!";
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"MAILER-DAEMON\", \"email\": \"postmaster@example.com\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"subject\": \"" + messageSubject + "\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body(requestBody)
+        // When
+        .when()
+                .post("/jmap");
+
+        // Then
+        with()
+                .accept(ContentType.JSON)
+                .contentType(ContentType.JSON)
+                .header("Authorization", accessToken.serialize())
+                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+        .post("/jmap")
+        .then()
+                .body(NAME, equalTo("messages"))
+                .body(ARGUMENTS + ".list", hasSize(1))
+                .body(ARGUMENTS + ".list[0].subject", equalTo(messageSubject))
+                .log().ifValidationFails();
+    }
 }
\ No newline at end of file


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


[2/9] james-project git commit: JAMES-1692 message processors are injected

Posted by ad...@apache.org.
JAMES-1692 message processors are injected

... through SetMessagesMethod's ctor


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

Branch: refs/heads/master
Commit: b21eae3f04c024cb80436702e799bd1af60952b9
Parents: 99ac325
Author: Fabien Vignon <fv...@linagora.com>
Authored: Wed Feb 17 11:40:18 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:42:53 2016 +0100

----------------------------------------------------------------------
 .../org/apache/james/jmap/MethodsModule.java    | 13 +++++++++++++
 .../james/jmap/methods/SetMessagesMethod.java   | 20 +++++++++++---------
 2 files changed, 24 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/b21eae3f/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
----------------------------------------------------------------------
diff --git a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
index 4e57d20..01257da 100644
--- a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
+++ b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.jmap;
 
+import java.util.List;
+
 import org.apache.james.jmap.json.ObjectMapperFactory;
 import org.apache.james.jmap.methods.GetMailboxesMethod;
 import org.apache.james.jmap.methods.GetMessageListMethod;
@@ -30,10 +32,13 @@ import org.apache.james.jmap.methods.JmapResponseWriterImpl;
 import org.apache.james.jmap.methods.Method;
 import org.apache.james.jmap.methods.SetMessagesCreationProcessor;
 import org.apache.james.jmap.methods.SetMessagesMethod;
+import org.apache.james.jmap.methods.SetMessagesProcessor;
 import org.apache.james.jmap.methods.SetMessagesUpdateProcessor;
 import org.apache.james.mailbox.cassandra.CassandraId;
 
+import com.google.common.collect.ImmutableList;
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.multibindings.Multibinder;
@@ -58,4 +63,12 @@ public class MethodsModule extends AbstractModule {
         bind(SetMessagesCreationProcessor.class).to(new TypeLiteral<SetMessagesCreationProcessor<CassandraId>>(){});
     }
 
+    @Provides
+    public List<SetMessagesProcessor<CassandraId>> setMessagesProcessors(
+            SetMessagesUpdateProcessor<CassandraId> messageUpdater,
+            SetMessagesCreationProcessor<CassandraId> messageCreator) {
+
+        return ImmutableList.of( messageUpdater, messageCreator);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/b21eae3f/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
index 5202026..7bb606b 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
@@ -43,6 +43,7 @@ import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,18 +56,15 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method {
 
     private final MailboxMapperFactory<Id> mailboxMapperFactory;
     private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
-    private final SetMessagesUpdateProcessor<Id> messageUpdater;
-    private final SetMessagesCreationProcessor messageCreator;
+    private final List<SetMessagesProcessor<Id>> messagesProcessors;
 
     @Inject
     @VisibleForTesting SetMessagesMethod(MailboxMapperFactory<Id> mailboxMapperFactory,
                                          MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory,
-                                         SetMessagesUpdateProcessor<Id> messageUpdater,
-                                         SetMessagesCreationProcessor messageCreator) {
+                                         List<SetMessagesProcessor<Id>> messagesProcessors) {
         this.mailboxMapperFactory = mailboxMapperFactory;
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
-        this.messageUpdater = messageUpdater;
-        this.messageCreator = messageCreator;
+        this.messagesProcessors = messagesProcessors;
     }
 
     @Override
@@ -99,9 +97,13 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method {
     private SetMessagesResponse setMessagesResponse(SetMessagesRequest request, MailboxSession mailboxSession) throws MailboxException {
         SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder();
         processDestroy(request.getDestroy(), mailboxSession, responseBuilder);
-        messageUpdater.process(request, mailboxSession).mergeInto(responseBuilder);
-        messageCreator.process(request, mailboxSession).mergeInto(responseBuilder);
-        return responseBuilder.build();
+        return messagesProcessors.stream()
+                .map(processor -> processor.process(request, mailboxSession))
+                .reduce(responseBuilder,
+                        (builder, resp) -> resp.mergeInto(builder) ,
+                        (builder1, builder2) -> builder2.build().mergeInto(builder1)
+                )
+                .build();
     }
 
     private void processDestroy(List<MessageId> messageIds, MailboxSession mailboxSession, SetMessagesResponse.Builder responseBuilder) throws MailboxException {


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


[6/9] james-project git commit: JAMES-1692 extract SetMessagesMethod.processDestroy to a class

Posted by ad...@apache.org.
JAMES-1692 extract SetMessagesMethod.processDestroy to a class


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

Branch: refs/heads/master
Commit: 8983dbbe0512b394d1063f5c5ee585e5873fc30d
Parents: 9388764
Author: Fabien Vignon <fv...@linagora.com>
Authored: Fri Feb 19 18:25:57 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:44:51 2016 +0100

----------------------------------------------------------------------
 .../org/apache/james/jmap/MethodsModule.java    |  19 +--
 .../SetMessagesDestructionProcessor.java        | 116 +++++++++++++++++++
 .../james/jmap/methods/SetMessagesMethod.java   |  79 +------------
 3 files changed, 126 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/8983dbbe/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
----------------------------------------------------------------------
diff --git a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
index 01257da..b5d254a 100644
--- a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
+++ b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java
@@ -19,8 +19,6 @@
 
 package org.apache.james.jmap;
 
-import java.util.List;
-
 import org.apache.james.jmap.json.ObjectMapperFactory;
 import org.apache.james.jmap.methods.GetMailboxesMethod;
 import org.apache.james.jmap.methods.GetMessageListMethod;
@@ -31,14 +29,13 @@ import org.apache.james.jmap.methods.JmapResponseWriter;
 import org.apache.james.jmap.methods.JmapResponseWriterImpl;
 import org.apache.james.jmap.methods.Method;
 import org.apache.james.jmap.methods.SetMessagesCreationProcessor;
+import org.apache.james.jmap.methods.SetMessagesDestructionProcessor;
 import org.apache.james.jmap.methods.SetMessagesMethod;
 import org.apache.james.jmap.methods.SetMessagesProcessor;
 import org.apache.james.jmap.methods.SetMessagesUpdateProcessor;
 import org.apache.james.mailbox.cassandra.CassandraId;
 
-import com.google.common.collect.ImmutableList;
 import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.multibindings.Multibinder;
@@ -59,16 +56,10 @@ public class MethodsModule extends AbstractModule {
         methods.addBinding().to(new TypeLiteral<GetMessageListMethod<CassandraId>>(){});
         methods.addBinding().to(new TypeLiteral<GetMessagesMethod<CassandraId>>(){});
         methods.addBinding().to(new TypeLiteral<SetMessagesMethod<CassandraId>>(){});
-        bind(SetMessagesUpdateProcessor.class).to(new TypeLiteral<SetMessagesUpdateProcessor<CassandraId>>(){});
-        bind(SetMessagesCreationProcessor.class).to(new TypeLiteral<SetMessagesCreationProcessor<CassandraId>>(){});
-    }
 
-    @Provides
-    public List<SetMessagesProcessor<CassandraId>> setMessagesProcessors(
-            SetMessagesUpdateProcessor<CassandraId> messageUpdater,
-            SetMessagesCreationProcessor<CassandraId> messageCreator) {
-
-        return ImmutableList.of( messageUpdater, messageCreator);
+        Multibinder<SetMessagesProcessor<CassandraId>> setMessagesProcessors = Multibinder.newSetBinder(binder(), new TypeLiteral<SetMessagesProcessor<CassandraId>>(){});
+        setMessagesProcessors.addBinding().to(new TypeLiteral<SetMessagesUpdateProcessor<CassandraId>>(){});
+        setMessagesProcessors.addBinding().to(new TypeLiteral<SetMessagesCreationProcessor<CassandraId>>(){});
+        setMessagesProcessors.addBinding().to(new TypeLiteral<SetMessagesDestructionProcessor<CassandraId>>(){});
     }
-
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/8983dbbe/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesDestructionProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesDestructionProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesDestructionProcessor.java
new file mode 100644
index 0000000..9424fff
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesDestructionProcessor.java
@@ -0,0 +1,116 @@
+/****************************************************************
+ * 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.jmap.methods;
+
+import java.util.Iterator;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+
+import org.apache.james.jmap.exceptions.MessageNotFoundException;
+import org.apache.james.jmap.model.MessageId;
+import org.apache.james.jmap.model.SetError;
+import org.apache.james.jmap.model.SetMessagesRequest;
+import org.apache.james.jmap.model.SetMessagesResponse;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
+import org.apache.james.mailbox.store.mail.MessageMapper;
+import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+
+public class SetMessagesDestructionProcessor<Id extends MailboxId> implements SetMessagesProcessor<Id> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesCreationProcessor.class);
+    private static final int LIMIT_BY_ONE = 1;
+
+    private final MailboxMapperFactory<Id> mailboxMapperFactory;
+    private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
+
+    @Inject
+    @VisibleForTesting
+    SetMessagesDestructionProcessor(MailboxMapperFactory<Id> mailboxMapperFactory,
+                                           MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory) {
+        this.mailboxMapperFactory = mailboxMapperFactory;
+        this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
+    }
+
+    @Override
+    public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) {
+        MessageMapper<Id> messageMapper;
+        try {
+            messageMapper = mailboxSessionMapperFactory.createMessageMapper(mailboxSession);
+        } catch (MailboxException e) {
+            throw Throwables.propagate(e);
+        }
+        return request.getDestroy().stream()
+                .map(delete(messageMapper, mailboxSession))
+                .reduce(SetMessagesResponse.builder(),  SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner)
+                .build();
+    }
+
+    private Function<? super MessageId, SetMessagesResponse> delete(MessageMapper<Id> messageMapper, MailboxSession mailboxSession) {
+        return (messageId) -> {
+            try {
+                Mailbox<Id> mailbox = mailboxMapperFactory
+                        .getMailboxMapper(mailboxSession)
+                        .findMailboxByPath(messageId.getMailboxPath());
+
+                MailboxMessage<Id> mailboxMessage = getMailboxMessage(messageMapper, messageId, mailbox);
+
+                messageMapper.delete(mailbox, mailboxMessage);
+                return SetMessagesResponse.builder().destroyed(messageId).build();
+            } catch (MessageNotFoundException e) {
+                return SetMessagesResponse.builder().notDestroyed(messageId,
+                        SetError.builder()
+                                .type("notFound")
+                                .description("The message " + messageId.serialize() + " can't be found")
+                                .build())
+                        .build();
+            } catch (MailboxException e) {
+                LOGGER.error("An error occurred when deleting a message", e);
+                return SetMessagesResponse.builder().notDestroyed(messageId,
+                        SetError.builder()
+                                .type("anErrorOccurred")
+                                .description("An error occurred while deleting message " + messageId.serialize())
+                                .build())
+                        .build();
+            }
+        };
+    }
+
+    private MailboxMessage<Id> getMailboxMessage(MessageMapper<Id> messageMapper, MessageId messageId, Mailbox<Id> mailbox)
+            throws MailboxException, MessageNotFoundException {
+
+        Iterator<MailboxMessage<Id>> mailboxMessage = messageMapper.findInMailbox(mailbox, MessageRange.one(messageId.getUid()), MessageMapper.FetchType.Metadata, LIMIT_BY_ONE);
+        if (!mailboxMessage.hasNext()) {
+            throw new MessageNotFoundException();
+        }
+        return mailboxMessage.next();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/8983dbbe/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
index 7bb606b..76c97ee 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java
@@ -19,51 +19,30 @@
 
 package org.apache.james.jmap.methods;
 
-import java.util.*;
-import java.util.function.Consumer;
+import java.util.Set;
 import java.util.stream.Stream;
+
 import javax.inject.Inject;
 
-import org.apache.james.jmap.exceptions.MessageNotFoundException;
 import org.apache.james.jmap.model.ClientId;
-import org.apache.james.jmap.model.MessageId;
-import org.apache.james.jmap.model.SetError;
 import org.apache.james.jmap.model.SetMessagesRequest;
 import org.apache.james.jmap.model.SetMessagesResponse;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
-import org.apache.james.mailbox.model.MessageRange;
-import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
-import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
-import org.apache.james.mailbox.store.mail.MessageMapper;
-import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
-import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.mail.model.MailboxId;
-import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 public class SetMessagesMethod<Id extends MailboxId> implements Method {
 
-    private static final int LIMIT_BY_ONE = 1;
-    private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesMethod.class);
     private static final Method.Request.Name METHOD_NAME = Method.Request.name("setMessages");
     private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("messagesSet");
 
-    private final MailboxMapperFactory<Id> mailboxMapperFactory;
-    private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory;
-    private final List<SetMessagesProcessor<Id>> messagesProcessors;
+    private final Set<SetMessagesProcessor<Id>> messagesProcessors;
 
     @Inject
-    @VisibleForTesting SetMessagesMethod(MailboxMapperFactory<Id> mailboxMapperFactory,
-                                         MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory,
-                                         List<SetMessagesProcessor<Id>> messagesProcessors) {
-        this.mailboxMapperFactory = mailboxMapperFactory;
-        this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
+    @VisibleForTesting SetMessagesMethod(Set<SetMessagesProcessor<Id>> messagesProcessors) {
         this.messagesProcessors = messagesProcessors;
     }
 
@@ -95,60 +74,12 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method {
     }
 
     private SetMessagesResponse setMessagesResponse(SetMessagesRequest request, MailboxSession mailboxSession) throws MailboxException {
-        SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder();
-        processDestroy(request.getDestroy(), mailboxSession, responseBuilder);
         return messagesProcessors.stream()
                 .map(processor -> processor.process(request, mailboxSession))
-                .reduce(responseBuilder,
+                .reduce(SetMessagesResponse.builder(),
                         (builder, resp) -> resp.mergeInto(builder) ,
                         (builder1, builder2) -> builder2.build().mergeInto(builder1)
                 )
                 .build();
     }
-
-    private void processDestroy(List<MessageId> messageIds, MailboxSession mailboxSession, SetMessagesResponse.Builder responseBuilder) throws MailboxException {
-        MessageMapper<Id> messageMapper = mailboxSessionMapperFactory.createMessageMapper(mailboxSession);
-        Consumer<? super MessageId> delete = delete(messageMapper, mailboxSession, responseBuilder);
-
-        messageIds.stream()
-            .forEach(delete);
-    }
-
-    private Consumer<? super MessageId> delete(MessageMapper<Id> messageMapper, MailboxSession mailboxSession, SetMessagesResponse.Builder responseBuilder) {
-        return (messageId) -> {
-            try {
-                Mailbox<Id> mailbox = mailboxMapperFactory
-                        .getMailboxMapper(mailboxSession)
-                        .findMailboxByPath(messageId.getMailboxPath());
-
-                MailboxMessage<Id> mailboxMessage = getMailboxMessage(messageMapper, messageId, mailbox);
-
-                messageMapper.delete(mailbox, mailboxMessage);
-                responseBuilder.destroyed(messageId);
-            } catch (MessageNotFoundException e) {
-                responseBuilder.notDestroyed(messageId,
-                        SetError.builder()
-                        .type("notFound")
-                        .description("The message " + messageId.serialize() + " can't be found")
-                        .build());
-            } catch (MailboxException e) {
-                LOGGER.error("An error occurred when deleting a message", e);
-                responseBuilder.notDestroyed(messageId,
-                        SetError.builder()
-                        .type("anErrorOccurred")
-                        .description("An error occurred while deleting message " + messageId.serialize())
-                        .build());
-            }
-        };
-    }
-
-    private MailboxMessage<Id> getMailboxMessage(MessageMapper<Id> messageMapper, MessageId messageId, Mailbox<Id> mailbox) 
-            throws MailboxException, MessageNotFoundException {
-
-        Iterator<MailboxMessage<Id>> mailboxMessage = messageMapper.findInMailbox(mailbox, MessageRange.one(messageId.getUid()), FetchType.Metadata, LIMIT_BY_ONE);
-        if (!mailboxMessage.hasNext()) {
-            throw new MessageNotFoundException();
-        }
-        return mailboxMessage.next();
-    }
 }


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


[4/9] james-project git commit: JAMES-1692 setMessages.create is actually a map of ids and messages

Posted by ad...@apache.org.
JAMES-1692 setMessages.create is actually a map of ids and messages

... not just a list of message


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

Branch: refs/heads/master
Commit: 0625fb5dc4bb384c58c748c58ebb9f760718e469
Parents: 7c7f377
Author: Fabien Vignon <fv...@linagora.com>
Authored: Fri Feb 12 18:43:49 2016 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Mar 1 15:42:53 2016 +0100

----------------------------------------------------------------------
 .../james/jmap/model/SetMessagesRequest.java    | 16 +++++++--------
 .../jmap/model/SetMessagesRequestTest.java      | 21 ++------------------
 2 files changed, 9 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/0625fb5d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
index ba81c35..aadb886 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java
@@ -48,13 +48,13 @@ public class SetMessagesRequest implements JmapRequest {
 
         private String accountId;
         private String ifInState;
-        private ImmutableList.Builder<Message> create;
+        private ImmutableMap.Builder<MessageId, Message> create;
         private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider;
 
         private ImmutableList.Builder<MessageId> destroy;
 
         private Builder() {
-            create = ImmutableList.builder();
+            create = ImmutableMap.builder();
             updatesProvider = ImmutableMap.builder();
             destroy = ImmutableList.builder();
         }
@@ -73,10 +73,8 @@ public class SetMessagesRequest implements JmapRequest {
             return this;
         }
 
-        public Builder create(List<Message> create) {
-            if (create != null && !create.isEmpty()) {
-                throw new NotImplementedException();
-            }
+        public Builder create(Map<MessageId, Message> creates) {
+            this.create.putAll(creates);
             return this;
         }
 
@@ -97,11 +95,11 @@ public class SetMessagesRequest implements JmapRequest {
 
     private final Optional<String> accountId;
     private final Optional<String> ifInState;
-    private final List<Message> create;
+    private final Map<MessageId, Message> create;
     private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update;
     private final List<MessageId> destroy;
 
-    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, List<Message> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
+    @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<MessageId, Message> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>>  update, List<MessageId> destroy) {
         this.accountId = accountId;
         this.ifInState = ifInState;
         this.create = create;
@@ -117,7 +115,7 @@ public class SetMessagesRequest implements JmapRequest {
         return ifInState;
     }
 
-    public List<Message> getCreate() {
+    public Map<MessageId, Message> getCreate() {
         return create;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/0625fb5d/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesRequestTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesRequestTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesRequestTest.java
index 0a079b6..7be03e1 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesRequestTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesRequestTest.java
@@ -22,7 +22,6 @@ package org.apache.james.jmap.model;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-import java.time.ZonedDateTime;
 import java.util.Optional;
 
 import org.apache.commons.lang.NotImplementedException;
@@ -46,31 +45,15 @@ public class SetMessagesRequestTest {
     }
 
     @Test
-    public void builderShouldThrowWhenCreateIsNotEmpty() {
-        assertThatThrownBy(() -> SetMessagesRequest.builder().create(ImmutableList.of(Message.builder()
-                .id(MessageId.of("user|create|1"))
-                .blobId("blobId")
-                .threadId("threadId")
-                .mailboxIds(ImmutableList.of("mailboxId"))
-                .headers(ImmutableMap.of("key", "value"))
-                .subject("subject")
-                .size(123)
-                .date(ZonedDateTime.now())
-                .preview("preview")
-                .build())))
-            .isInstanceOf(NotImplementedException.class);
-    }
-
-    @Test
     public void builderShouldWork() {
         ImmutableList<MessageId> destroy = ImmutableList.of(MessageId.of("user|destroy|1"));
 
-        SetMessagesRequest expected = new SetMessagesRequest(Optional.empty(), Optional.empty(), ImmutableList.of(), ImmutableMap.of(), destroy);
+        SetMessagesRequest expected = new SetMessagesRequest(Optional.empty(), Optional.empty(), ImmutableMap.of(), ImmutableMap.of(), destroy);
 
         SetMessagesRequest setMessagesRequest = SetMessagesRequest.builder()
             .accountId(null)
             .ifInState(null)
-            .create(ImmutableList.of())
+            .create(ImmutableMap.of())
             .update(ImmutableMap.of())
             .destroy(destroy)
             .build();


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