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 rc...@apache.org on 2020/04/10 02:02:08 UTC

[james-project] branch master updated (b5ed9c2 -> f6ec49a)

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

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


    from b5ed9c2  JAMES-3144 Increase ElasticSearch timeout in RabbitMQReindexingWithEventDeadLettersTest
     new 96bcd6e  Remove leading line breaks
     new f44aeed  JAMES-3136 Switch imapUidDAO into source of truth for CassandraMessageMapper
     new 0269543  JAMES-3136 Limit message projection inconsistency with a retry strategy
     new 52d55c5  JAMES-3136 Failure tests for CassandraMessageIdMapper
     new c8e97f6  JAMES-3136 Limit message projection inconsistency with a retry strategy
     new d5b4b3a  JAMES-3136 Update message denormalisation tables concurrently
     new f59c6df  JAMES-3092 Move JMAPUrls to jmap module
     new 43bfc1d  JAMES-3092 Implement Version, Verb and Endpoint
     new 6431f55  JAMES-3092 Instauring the Y structure with jmap-draft
     new 6d658f1  JAMES-3092 Simplify the Y versioning routing in jmap
     new 075a629  JAMES-3092 Add a JMAPRoutesHandler component to handle version and routes injection
     new 9af645e  JAMES-3092 Add a builder for JMAPRoute
     new 1c3b7f5  JAMES-3092 Using HttpCore library to parse correctly the version parameter in accept header
     new a27dcdb  JAMES-3092 Set up parameter resolving
     new f6ec49a  JAMES-3092 Implement VersionParser to parse the version header against an injected set of supported versions

The 15 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 dockerfiles/run/spring/destination/conf/log4j2.xml |   1 -
 .../cassandra/mail/CassandraMessageIdMapper.java   |  29 ++--
 .../cassandra/mail/CassandraMessageMapper.java     |  19 ++-
 .../mail/CassandraMessageIdMapperTest.java         | 147 +++++++++++++++++
 .../cassandra/mail/CassandraMessageMapperTest.java | 148 +++++++++++++++++
 .../store/mail/model/MessageIdMapperTest.java      |   6 +-
 .../store/mail/model/MessageMapperTest.java        |  24 +--
 server/app/src/main/resources/log4j2.xml           |   1 -
 .../org/apache/james/jmap/draft/JMAPModule.java    |   4 +
 .../modules/protocols/JMAPDraftServerModule.java   |  21 ++-
 .../james/jmap/http/AuthenticationRoutes.java      |  33 +++-
 .../org/apache/james/jmap/http/DownloadRoutes.java |  41 ++++-
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  |  21 ++-
 .../org/apache/james/jmap/http/UploadRoutes.java   |  21 ++-
 .../apache/james/jmap/http/JMAPApiRoutesTest.java  |  11 +-
 server/protocols/jmap/pom.xml                      |   9 +
 .../main/java/org/apache/james/jmap/Endpoint.java  |  50 +++---
 .../main/java/org/apache/james/jmap/JMAPRoute.java |  96 +++++++++++
 .../java/org/apache/james/jmap/JMAPRoutes.java     |  13 +-
 .../org/apache/james/jmap/JMAPRoutesHandler.java}  |  29 ++--
 .../java/org/apache/james/jmap/JMAPServer.java     |  27 ++-
 .../main/java/org/apache/james/jmap}/JMAPUrls.java |   2 +-
 .../org/apache/james/jmap/UriPathTemplate.java     | 169 +++++++++++++++++++
 .../main/java/org/apache/james/jmap/Version.java   |  25 +--
 .../java/org/apache/james/jmap/VersionParser.java  |  77 +++++++++
 .../java/org/apache/james/jmap/EndpointTest.java   |  11 +-
 .../java/org/apache/james/jmap/JMAPServerTest.java | 182 ++++++++++++++++++++-
 .../org/apache/james/jmap/VersionParserTest.java}  |  57 ++++---
 .../java/org/apache/james/jmap/VersionTest.java    |  10 +-
 29 files changed, 1104 insertions(+), 180 deletions(-)
 copy mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageIdDto.java => server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java (57%)
 create mode 100644 server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
 copy server/{container/guice/guice-common/src/main/java/org/apache/james/utils/Startables.java => protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java} (70%)
 rename server/protocols/{jmap-draft/src/main/java/org/apache/james/jmap/http => jmap/src/main/java/org/apache/james/jmap}/JMAPUrls.java (97%)
 create mode 100644 server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
 copy mailbox/api/src/main/java/org/apache/james/mailbox/events/GenericGroup.java => server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java (73%)
 create mode 100644 server/protocols/jmap/src/main/java/org/apache/james/jmap/VersionParser.java
 copy mailbox/api/src/test/java/org/apache/james/mailbox/indexer/ReindexingExecutionFailuresTest.java => server/protocols/jmap/src/test/java/org/apache/james/jmap/EndpointTest.java (88%)
 copy server/{container/guice/guice-utils/src/test/java/org/apache/james/utils/PackageNameTest.java => protocols/jmap/src/test/java/org/apache/james/jmap/VersionParserTest.java} (60%)
 copy protocols/imap/src/test/java/org/apache/james/imap/api/TagTest.java => server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java (90%)


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


[james-project] 01/15: Remove leading line breaks

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 96bcd6e24729a9b120ba403425f652bcdf0237ab
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Apr 9 10:00:29 2020 +0700

    Remove leading line breaks
---
 dockerfiles/run/spring/destination/conf/log4j2.xml | 1 -
 server/app/src/main/resources/log4j2.xml           | 1 -
 2 files changed, 2 deletions(-)

diff --git a/dockerfiles/run/spring/destination/conf/log4j2.xml b/dockerfiles/run/spring/destination/conf/log4j2.xml
index c89ad2e..f8c9fad 100644
--- a/dockerfiles/run/spring/destination/conf/log4j2.xml
+++ b/dockerfiles/run/spring/destination/conf/log4j2.xml
@@ -1,4 +1,3 @@
-
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="INFO" monitorInterval="30">
     <Properties>
diff --git a/server/app/src/main/resources/log4j2.xml b/server/app/src/main/resources/log4j2.xml
index c89ad2e..f8c9fad 100644
--- a/server/app/src/main/resources/log4j2.xml
+++ b/server/app/src/main/resources/log4j2.xml
@@ -1,4 +1,3 @@
-
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="INFO" monitorInterval="30">
     <Properties>


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


[james-project] 14/15: JAMES-3092 Set up parameter resolving

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a27dcdb0ded3379847a290cec596b416fb4fb39b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 3 10:35:05 2020 +0700

    JAMES-3092 Set up parameter resolving
---
 .../main/java/org/apache/james/jmap/Endpoint.java  |  4 +++
 .../main/java/org/apache/james/jmap/JMAPRoute.java |  9 ++++++-
 .../org/apache/james/jmap/UriPathTemplate.java     | 30 ++++++++++++++++++++++
 3 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
index 222d40b..a1a3f8c 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
@@ -48,6 +48,10 @@ public class Endpoint {
             && uriPathTemplate.matches(request.uri());
     }
 
+    UriPathTemplate getUriPathTemplate() {
+        return uriPathTemplate;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof Endpoint) {
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
index 8ad8ecf..d81e47b 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
@@ -58,7 +58,14 @@ public class JMAPRoute {
             }
 
             private JMAPRoute build(Action action) {
-                return new JMAPRoute(endpoint, action);
+                return new JMAPRoute(endpoint, actionWithParameterResolving(action));
+            }
+
+            Action actionWithParameterResolving(Action action) {
+                return (req, res) ->
+                    action.handleRequest(
+                        req.paramsResolver(s -> endpoint.getUriPathTemplate().match(s)),
+                        res);
             }
         }
     }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
index a9b3759..6c158a9 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
@@ -136,4 +136,34 @@ public class UriPathTemplate {
         return m;
     }
 
+    /**
+     * Matches the template against the given {@code uri} returning a map of path
+     * parameters extracted from the uri, keyed by the names in the template. If the
+     * uri does not match, or there are no path parameters, an empty map is returned.
+     *
+     * @param uri The uri to match
+     *
+     * @return the path parameters from the uri. Never {@code null}.
+     */
+    final Map<String, String> match(String uri) {
+        Map<String, String> pathParameters = vars.get(uri);
+        if (null != pathParameters) {
+            return pathParameters;
+        }
+
+        pathParameters = new HashMap<>();
+        Matcher m = matcher(uri);
+        if (m.matches()) {
+            int i = 1;
+            for (String name : pathVariables) {
+                String val = m.group(i++);
+                pathParameters.put(name, val);
+            }
+        }
+        synchronized (vars) {
+            vars.put(uri, pathParameters);
+        }
+
+        return pathParameters;
+    }
 }


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


[james-project] 02/15: JAMES-3136 Switch imapUidDAO into source of truth for CassandraMessageMapper

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f44aeed5630bc38316424c1e4d218fb360b4129b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 10:30:08 2020 +0700

    JAMES-3136 Switch imapUidDAO into source of truth for CassandraMessageMapper
    
    Write to it first, then denormalize to `messageIdTable`.
    
    Tests the various failures that can occur on the write path for
    CassandraMessageMapper.
---
 .../cassandra/mail/CassandraMessageMapper.java     |   6 +-
 .../cassandra/mail/CassandraMessageMapperTest.java | 133 +++++++++++++++++++++
 .../store/mail/model/MessageMapperTest.java        |  24 ++--
 3 files changed, 147 insertions(+), 16 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index 23dbc07..807b891 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -405,10 +405,8 @@ public class CassandraMessageMapper implements MessageMapper {
                 .flags(message.createFlags())
                 .modSeq(message.getModSeq())
                 .build();
-        return Flux.merge(
-                messageIdDAO.insert(composedMessageIdWithMetaData),
-                imapUidDAO.insert(composedMessageIdWithMetaData))
-            .then();
+        return imapUidDAO.insert(composedMessageIdWithMetaData)
+            .then(messageIdDAO.insert(composedMessageIdWithMetaData));
     }
 
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
index acfacf3..fb647c0 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
@@ -19,11 +19,25 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
+
+import java.util.Optional;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+import com.github.fge.lambdas.Throwing;
+
 class CassandraMessageMapperTest extends MessageMapperTest {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MailboxAggregateModule.MODULE);
@@ -32,4 +46,123 @@ class CassandraMessageMapperTest extends MessageMapperTest {
     protected MapperProvider createMapperProvider() {
         return new CassandraMapperProvider(cassandraCluster.getCassandraCluster());
     }
+
+    @Nested
+    class FailureTesting {
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailToPersistInMessageDAO(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO messageV2 (messageId,internalDate,bodyStartOctet,fullContentOctets,bodyOctets,bodyContent,headerContent,properties,textualLineCount,attachments)"));
+
+            try {
+                messageMapper.add(benwaInboxMailbox, message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                    .toIterable()
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistBlobParts(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO blobParts (id,chunkNumber,data) VALUES (:id,:chunkNumber,:data);"));
+
+            try {
+                messageMapper.add(benwaInboxMailbox, message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                    .toIterable()
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistBlobs(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO blobs (id,position) VALUES (:id,:position);"));
+
+            try {
+                messageMapper.add(benwaInboxMailbox, message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                    .toIterable()
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistInImapUidTable(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO imapUidTable (messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            try {
+                messageMapper.add(benwaInboxMailbox, message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                    .toIterable()
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void addShouldPersistInTableOfTruthWhenMessageIdTableWritesFails(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            try {
+                messageMapper.add(benwaInboxMailbox, message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdToImapUidDAO imapUidDAO = new CassandraMessageIdToImapUidDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                    .toIterable()
+                    .isEmpty();
+                softly.assertThat(imapUidDAO.retrieve((CassandraMessageId) message1.getMessageId(), Optional.empty()).collectList().block())
+                    .hasSize(1);
+            }));
+        }
+    }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
index 11cf811..ae67a36 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
@@ -77,18 +77,18 @@ public abstract class MessageMapperTest {
     private static final Username BENWA = Username.of("benwa");
 
     private MapperProvider mapperProvider;
-    private MessageMapper messageMapper;
+    protected MessageMapper messageMapper;
     private MailboxMapper mailboxMapper;
 
-    private Mailbox benwaInboxMailbox;
-    private Mailbox benwaWorkMailbox;
-    
-    private MailboxMessage message1;
-    private MailboxMessage message2;
-    private MailboxMessage message3;
-    private MailboxMessage message4;
-    private MailboxMessage message5;
-    private MailboxMessage message6;
+    protected Mailbox benwaInboxMailbox;
+    protected Mailbox benwaWorkMailbox;
+
+    protected MailboxMessage message1;
+    protected MailboxMessage message2;
+    protected MailboxMessage message3;
+    protected MailboxMessage message4;
+    protected MailboxMessage message5;
+    protected MailboxMessage message6;
 
     protected abstract MapperProvider createMapperProvider();
 
@@ -1203,8 +1203,8 @@ public abstract class MessageMapperTest {
     private Mailbox createMailbox(MailboxPath mailboxPath) throws MailboxException {
         return mailboxMapper.create(mailboxPath, UID_VALIDITY);
     }
-    
-    private void saveMessages() throws MailboxException {
+
+    protected void saveMessages() throws MailboxException {
         messageMapper.add(benwaInboxMailbox, message1);
         message1.setModSeq(messageMapper.getHighestModSeq(benwaInboxMailbox));
         messageMapper.add(benwaInboxMailbox, message2);


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


[james-project] 11/15: JAMES-3092 Add a JMAPRoutesHandler component to handle version and routes injection

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 075a6291f08b4c261cd3ef7261dbb668ae5d24d9
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Mar 30 15:54:26 2020 +0700

    JAMES-3092 Add a JMAPRoutesHandler component to handle version and routes injection
---
 .../modules/protocols/JMAPDraftServerModule.java   | 21 ++++++----
 .../james/jmap/http/AuthenticationRoutes.java      |  9 ++---
 .../org/apache/james/jmap/http/DownloadRoutes.java | 13 +++---
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  |  5 +--
 .../org/apache/james/jmap/http/UploadRoutes.java   |  5 +--
 .../main/java/org/apache/james/jmap/JMAPRoute.java | 32 +--------------
 .../{JMAPRoute.java => JMAPRoutesHandler.java}     | 47 +++++++++-------------
 .../java/org/apache/james/jmap/JMAPServer.java     | 12 +++---
 .../java/org/apache/james/jmap/JMAPServerTest.java | 30 ++++++++------
 9 files changed, 71 insertions(+), 103 deletions(-)

diff --git a/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/modules/protocols/JMAPDraftServerModule.java b/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/modules/protocols/JMAPDraftServerModule.java
index 97ee0c2..2ab7049 100644
--- a/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/modules/protocols/JMAPDraftServerModule.java
+++ b/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/modules/protocols/JMAPDraftServerModule.java
@@ -22,8 +22,9 @@ package org.apache.james.modules.protocols;
 import java.security.Security;
 
 import org.apache.james.jmap.JMAPConfiguration;
-import org.apache.james.jmap.JMAPRoutes;
+import org.apache.james.jmap.JMAPRoutesHandler;
 import org.apache.james.jmap.JMAPServer;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.JMAPModule;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.jmap.draft.MessageIdProbe;
@@ -48,12 +49,6 @@ public class JMAPDraftServerModule extends AbstractModule {
         install(new JMAPModule());
         Multibinder.newSetBinder(binder(), GuiceProbe.class).addBinding().to(JmapGuiceProbe.class);
         Multibinder.newSetBinder(binder(), GuiceProbe.class).addBinding().to(MessageIdProbe.class);
-        Multibinder<JMAPRoutes> routesBinder = Multibinder.newSetBinder(binder(), JMAPRoutes.class);
-
-        routesBinder.addBinding().to(AuthenticationRoutes.class);
-        routesBinder.addBinding().to(JMAPApiRoutes.class);
-        routesBinder.addBinding().to(UploadRoutes.class);
-        routesBinder.addBinding().to(DownloadRoutes.class);
     }
 
     @ProvidesIntoSet
@@ -69,6 +64,18 @@ public class JMAPDraftServerModule extends AbstractModule {
             });
     }
 
+    @ProvidesIntoSet
+    JMAPRoutesHandler routesHandler(AuthenticationRoutes authenticationRoutes,
+                                    JMAPApiRoutes jmapApiRoutes,
+                                    UploadRoutes uploadRoutes,
+                                    DownloadRoutes  downloadRoutes) {
+        return new JMAPRoutesHandler(Version.DRAFT,
+            authenticationRoutes,
+            jmapApiRoutes,
+            uploadRoutes,
+            downloadRoutes);
+    }
+
     private void registerPEMWithSecurityProvider() {
         Security.addProvider(new BouncyCastleProvider());
     }
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index 9412861..f9f9eb1 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -46,7 +46,6 @@ import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.JMAPUrls;
-import org.apache.james.jmap.Version;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.draft.api.AccessTokenManager;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
@@ -108,10 +107,10 @@ public class AuthenticationRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
-            new JMAPRoute(new Endpoint(HttpMethod.DELETE, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::delete)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, AUTHENTICATION), JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, AUTHENTICATION), JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
+            new JMAPRoute(new Endpoint(HttpMethod.DELETE, AUTHENTICATION), JMAPRoutes.corsHeaders(this::delete)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION), CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index c8b35a2..e0cc639 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -40,7 +40,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
@@ -101,12 +100,12 @@ public class DownloadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromId)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromId)),
-            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromIdAndName)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromIdAndName)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID), Version.DRAFT, CORS_CONTROL),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID), JMAPRoutes.corsHeaders(this::postFromId)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID), JMAPRoutes.corsHeaders(this::getFromId)),
+            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID_AND_NAME), JMAPRoutes.corsHeaders(this::postFromIdAndName)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID_AND_NAME), JMAPRoutes.corsHeaders(this::getFromIdAndName)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID), CORS_CONTROL),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index f574bff..6530171 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -34,7 +34,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
 import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
@@ -88,8 +87,8 @@ public class JMAPApiRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, JMAP), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, JMAP), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, JMAP), JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, JMAP), CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index 92c289d..b0f22ba 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -38,7 +38,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
 import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
@@ -90,8 +89,8 @@ public class UploadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, UPLOAD), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, UPLOAD), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, UPLOAD), JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, UPLOAD), CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
index aaa3909..c28cc73 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
@@ -19,12 +19,6 @@
 
 package org.apache.james.jmap;
 
-import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
-
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.stream.Stream;
-
 import org.reactivestreams.Publisher;
 
 import reactor.netty.http.server.HttpServerRequest;
@@ -35,15 +29,11 @@ public class JMAPRoute {
         Publisher<Void> handleRequest(HttpServerRequest request, HttpServerResponse response);
     }
 
-    private static final String JMAP_VERSION_HEADER = "jmapVersion=";
-
     private final Endpoint endpoint;
-    private final Version version;
     private final Action action;
 
-    public JMAPRoute(Endpoint endpoint, Version version, Action action) {
+    public JMAPRoute(Endpoint endpoint, Action action) {
         this.endpoint = endpoint;
-        this.version = version;
         this.action = action;
     }
 
@@ -51,29 +41,11 @@ public class JMAPRoute {
         return endpoint;
     }
 
-    public Version getVersion() {
-        return version;
-    }
-
     public Action getAction() {
         return action;
     }
 
     public boolean matches(HttpServerRequest request) {
-        return getVersion().equals(extractRequestVersionHeader(request))
-            && getEndpoint().matches(request);
-    }
-
-    private Version extractRequestVersionHeader(HttpServerRequest request) {
-        return Optional.ofNullable(request.requestHeaders().get(ACCEPT))
-                .map(s -> s.split(";"))
-                .map(Arrays::stream)
-                .orElse(Stream.of())
-            .map(value -> value.trim().toLowerCase())
-            .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
-            .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
-            .map(Version::of)
-            .findFirst()
-            .orElse(Version.DRAFT);
+        return endpoint.matches(request);
     }
 }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
similarity index 66%
copy from server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
copy to server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
index aaa3909..8c094f5 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
@@ -23,52 +23,41 @@ import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
 
 import java.util.Arrays;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Stream;
 
-import org.reactivestreams.Publisher;
+import com.google.common.collect.ImmutableSet;
 
 import reactor.netty.http.server.HttpServerRequest;
-import reactor.netty.http.server.HttpServerResponse;
 
-public class JMAPRoute {
-    public interface Action {
-        Publisher<Void> handleRequest(HttpServerRequest request, HttpServerResponse response);
-    }
-
-    private static final String JMAP_VERSION_HEADER = "jmapVersion=";
+public class JMAPRoutesHandler {
+    String JMAP_VERSION_HEADER = "jmapVersion=";
 
-    private final Endpoint endpoint;
     private final Version version;
-    private final Action action;
+    private final Set<JMAPRoutes> routes;
 
-    public JMAPRoute(Endpoint endpoint, Version version, Action action) {
-        this.endpoint = endpoint;
+    public JMAPRoutesHandler(Version version, JMAPRoutes... routes) {
         this.version = version;
-        this.action = action;
-    }
-
-    public Endpoint getEndpoint() {
-        return endpoint;
-    }
-
-    public Version getVersion() {
-        return version;
+        this.routes = ImmutableSet.copyOf(routes);
     }
 
-    public Action getAction() {
-        return action;
+    Stream<JMAPRoute> routes(HttpServerRequest request) {
+        if (matches(request)) {
+            return routes.stream()
+                .flatMap(JMAPRoutes::routes);
+        }
+        return Stream.of();
     }
 
-    public boolean matches(HttpServerRequest request) {
-        return getVersion().equals(extractRequestVersionHeader(request))
-            && getEndpoint().matches(request);
+    private boolean matches(HttpServerRequest request) {
+        return version.equals(extractRequestVersionHeader(request));
     }
 
     private Version extractRequestVersionHeader(HttpServerRequest request) {
         return Optional.ofNullable(request.requestHeaders().get(ACCEPT))
-                .map(s -> s.split(";"))
-                .map(Arrays::stream)
-                .orElse(Stream.of())
+            .map(s -> s.split(";"))
+            .map(Arrays::stream)
+            .orElse(Stream.of())
             .map(value -> value.trim().toLowerCase())
             .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
             .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index 6b354fe..1bbdd24 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -40,13 +40,13 @@ public class JMAPServer implements Startable {
     private static final int RANDOM_PORT = 0;
 
     private final JMAPConfiguration configuration;
-    private final Set<JMAPRoutes> jmapRoutes;
+    private final Set<JMAPRoutesHandler> jmapRoutesHandlers;
     private Optional<DisposableServer> server;
 
     @Inject
-    public JMAPServer(JMAPConfiguration configuration, Set<JMAPRoutes> jmapRoutes) {
+    public JMAPServer(JMAPConfiguration configuration, Set<JMAPRoutesHandler> jmapRoutesHandlers) {
         this.configuration = configuration;
-        this.jmapRoutes = jmapRoutes;
+        this.jmapRoutesHandlers = jmapRoutesHandlers;
         this.server = Optional.empty();
     }
 
@@ -74,8 +74,8 @@ public class JMAPServer implements Startable {
 
     private JMAPRoute.Action handleVersionRoute(HttpServerRequest request) {
         try {
-            return jmapRoutes.stream()
-                .flatMap(JMAPRoutes::routes)
+            return jmapRoutesHandlers.stream()
+                .flatMap(jmapRoutesHandler -> jmapRoutesHandler.routes(request))
                 .filter(jmapRoute -> jmapRoute.matches(request))
                 .map(JMAPRoute::getAction)
                 .findFirst()
@@ -85,8 +85,6 @@ public class JMAPServer implements Startable {
         }
     }
 
-
-
     @PreDestroy
     public void stop() {
         server.ifPresent(DisposableServer::disposeNow);
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index a0eec47..fe2e2f9 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -60,7 +60,7 @@ class JMAPServerTest {
         .enable()
         .randomPort()
         .build();
-    private static final ImmutableSet<JMAPRoutes> NO_ROUTES = ImmutableSet.of();
+    private static final ImmutableSet<JMAPRoutesHandler> NO_ROUTES_HANDLERS = ImmutableSet.of();
 
     private static final ImmutableSet<Endpoint> AUTHENTICATION_ENDPOINTS = ImmutableSet.of(
         new Endpoint(HttpMethod.POST, JMAPUrls.AUTHENTICATION),
@@ -70,15 +70,21 @@ class JMAPServerTest {
         new Endpoint(HttpMethod.POST, JMAPUrls.JMAP),
         new Endpoint(HttpMethod.DELETE, JMAPUrls.JMAP)
     );
-    private static final ImmutableSet<JMAPRoutes> FAKE_ROUTES = ImmutableSet.of(
-        new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.DRAFT),
-        new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.RFC8621),
-        new FakeJMAPRoutes(JMAP_ENDPOINTS, Version.DRAFT)
+
+    private static final ImmutableSet<JMAPRoutesHandler> FAKE_ROUTES_HANDLERS = ImmutableSet.of(
+        new JMAPRoutesHandler(
+            Version.DRAFT,
+            new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.DRAFT),
+            new FakeJMAPRoutes(JMAP_ENDPOINTS, Version.DRAFT)),
+        new JMAPRoutesHandler(
+            Version.RFC8621,
+            new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.RFC8621))
     );
 
+
     @Test
     void serverShouldAnswerWhenStarted() {
-        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES);
+        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS);
         jmapServer.start();
 
         try {
@@ -96,14 +102,14 @@ class JMAPServerTest {
 
     @Test
     void startShouldNotThrowWhenConfigurationDisabled() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
 
         assertThatCode(jmapServer::start).doesNotThrowAnyException();
     }
 
     @Test
     void stopShouldNotThrowWhenConfigurationDisabled() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
         jmapServer.start();
 
         assertThatCode(jmapServer::stop).doesNotThrowAnyException();
@@ -111,7 +117,7 @@ class JMAPServerTest {
 
     @Test
     void getPortShouldThrowWhenServerIsNotStarted() {
-        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES);
+        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS);
 
         assertThatThrownBy(jmapServer::getPort)
             .isInstanceOf(IllegalStateException.class);
@@ -119,7 +125,7 @@ class JMAPServerTest {
 
     @Test
     void getPortShouldThrowWhenDisabledConfiguration() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
         jmapServer.start();
 
         assertThatThrownBy(jmapServer::getPort)
@@ -132,7 +138,7 @@ class JMAPServerTest {
 
         @BeforeEach
         void setUp() {
-            server = new JMAPServer(TEST_CONFIGURATION, FAKE_ROUTES);
+            server = new JMAPServer(TEST_CONFIGURATION, FAKE_ROUTES_HANDLERS);
             server.start();
 
             RestAssured.requestSpecification = new RequestSpecBuilder()
@@ -220,7 +226,7 @@ class JMAPServerTest {
         @Override
         public Stream<JMAPRoute> routes() {
             return endpoints.stream()
-                .map(endpoint -> new JMAPRoute(endpoint, version, (request, response) -> sendVersionResponse(response)));
+                .map(endpoint -> new JMAPRoute(endpoint, (request, response) -> sendVersionResponse(response)));
         }
 
         @Override


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


[james-project] 12/15: JAMES-3092 Add a builder for JMAPRoute

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9af645e1352656f78033a47c7410fbcdde60ea50
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Mar 30 16:27:38 2020 +0700

    JAMES-3092 Add a builder for JMAPRoute
---
 .../james/jmap/http/AuthenticationRoutes.java      | 20 ++++++++---
 .../org/apache/james/jmap/http/DownloadRoutes.java | 30 ++++++++++++----
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  | 10 ++++--
 .../org/apache/james/jmap/http/UploadRoutes.java   | 10 ++++--
 .../main/java/org/apache/james/jmap/JMAPRoute.java | 40 +++++++++++++++++++++-
 .../java/org/apache/james/jmap/JMAPServerTest.java |  5 ++-
 6 files changed, 99 insertions(+), 16 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index f9f9eb1..70b0490 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -107,10 +107,22 @@ public class AuthenticationRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, AUTHENTICATION), JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, AUTHENTICATION), JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
-            new JMAPRoute(new Endpoint(HttpMethod.DELETE, AUTHENTICATION), JMAPRoutes.corsHeaders(this::delete)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION), CORS_CONTROL)
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.POST, AUTHENTICATION))
+                .action(this::post)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.GET, AUTHENTICATION))
+                .action(this::returnEndPointsResponse)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.DELETE, AUTHENTICATION))
+                .action(this::delete)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION))
+                .action(CORS_CONTROL)
+                .noCorsHeaders()
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index e0cc639..8ac0bc3 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -100,12 +100,30 @@ public class DownloadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID), JMAPRoutes.corsHeaders(this::postFromId)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID), JMAPRoutes.corsHeaders(this::getFromId)),
-            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID_AND_NAME), JMAPRoutes.corsHeaders(this::postFromIdAndName)),
-            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID_AND_NAME), JMAPRoutes.corsHeaders(this::getFromIdAndName)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID), CORS_CONTROL),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), CORS_CONTROL)
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID))
+                .action(this::postFromId)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID))
+                .action(this::getFromId)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID_AND_NAME))
+                .action(this::postFromIdAndName)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID_AND_NAME))
+                .action(this::getFromIdAndName)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID))
+                .action(CORS_CONTROL)
+                .noCorsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME))
+                .action(CORS_CONTROL)
+                .noCorsHeaders()
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index 6530171..7454afe 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -87,8 +87,14 @@ public class JMAPApiRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, JMAP), JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, JMAP), CORS_CONTROL)
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.POST, JMAP))
+                .action(this::post)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.OPTIONS, JMAP))
+                .action(CORS_CONTROL)
+                .noCorsHeaders()
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index b0f22ba..6788c69 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -89,8 +89,14 @@ public class UploadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(HttpMethod.POST, UPLOAD), JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, UPLOAD), CORS_CONTROL)
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.POST, UPLOAD))
+                .action(this::post)
+                .corsHeaders(),
+            JMAPRoute.builder()
+                .endpoint(new Endpoint(HttpMethod.OPTIONS, UPLOAD))
+                .action(CORS_CONTROL)
+                .noCorsHeaders()
         );
     }
 
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
index c28cc73..8ad8ecf 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
@@ -29,10 +29,48 @@ public class JMAPRoute {
         Publisher<Void> handleRequest(HttpServerRequest request, HttpServerResponse response);
     }
 
+    public static class Builder {
+        @FunctionalInterface
+        public interface RequireEndpoint {
+            RequireAction endpoint(Endpoint endpoint);
+        }
+
+        @FunctionalInterface
+        public interface RequireAction {
+            ReadyToBuild action(Action action);
+        }
+
+        public static class ReadyToBuild {
+            private final Endpoint endpoint;
+            private final Action action;
+
+            ReadyToBuild(Endpoint endpoint, Action action) {
+                this.endpoint = endpoint;
+                this.action = action;
+            }
+
+            public JMAPRoute corsHeaders() {
+                return build(JMAPRoutes.corsHeaders(action));
+            }
+
+            public JMAPRoute noCorsHeaders() {
+                return build(action);
+            }
+
+            private JMAPRoute build(Action action) {
+                return new JMAPRoute(endpoint, action);
+            }
+        }
+    }
+
+    public static Builder.RequireEndpoint builder() {
+        return endpoint -> action -> new Builder.ReadyToBuild(endpoint, action);
+    }
+
     private final Endpoint endpoint;
     private final Action action;
 
-    public JMAPRoute(Endpoint endpoint, Action action) {
+    private JMAPRoute(Endpoint endpoint, Action action) {
         this.endpoint = endpoint;
         this.action = action;
     }
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index fe2e2f9..759bbb3 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -226,7 +226,10 @@ class JMAPServerTest {
         @Override
         public Stream<JMAPRoute> routes() {
             return endpoints.stream()
-                .map(endpoint -> new JMAPRoute(endpoint, (request, response) -> sendVersionResponse(response)));
+                .map(endpoint -> JMAPRoute.builder()
+                    .endpoint(endpoint)
+                    .action((request, response) -> sendVersionResponse(response))
+                    .noCorsHeaders());
         }
 
         @Override


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


[james-project] 06/15: JAMES-3136 Update message denormalisation tables concurrently

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d5b4b3a4c53d34f231c9ae62d3c13151e0fcc56f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 16:03:31 2020 +0700

    JAMES-3136 Update message denormalisation tables concurrently
---
 .../cassandra/mail/CassandraMessageIdMapper.java   | 24 +++++++++++++---------
 .../cassandra/mail/CassandraMessageMapper.java     | 11 +++++-----
 2 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index 3cfd722..47cd80b 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -130,12 +130,9 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     public void save(MailboxMessage mailboxMessage) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
         mailboxMapper.findMailboxById(mailboxId);
-        ComposedMessageIdWithMetaData composedMessageIdWithMetaData = createMetadataFor(mailboxMessage);
+
         messageDAO.save(mailboxMessage)
-            .thenEmpty(imapUidDAO.insert(composedMessageIdWithMetaData))
-            .thenEmpty(messageIdDAO.insert(composedMessageIdWithMetaData)
-                .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF))
-            .thenEmpty(indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
+            .thenEmpty(saveMessageMetadata(mailboxMessage, mailboxId))
             .block();
     }
 
@@ -143,14 +140,21 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     public void copyInMailbox(MailboxMessage mailboxMessage) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
         mailboxMapper.findMailboxById(mailboxId);
-        ComposedMessageIdWithMetaData composedMessageIdWithMetaData = createMetadataFor(mailboxMessage);
-        Flux.merge(
-                imapUidDAO.insert(composedMessageIdWithMetaData),
-                messageIdDAO.insert(composedMessageIdWithMetaData))
-            .thenEmpty(indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
+
+        saveMessageMetadata(mailboxMessage, mailboxId)
             .block();
     }
 
+    private Mono<Void> saveMessageMetadata(MailboxMessage mailboxMessage, CassandraId mailboxId) {
+        ComposedMessageIdWithMetaData composedMessageIdWithMetaData = createMetadataFor(mailboxMessage);
+        return imapUidDAO.insert(composedMessageIdWithMetaData)
+            .thenEmpty(Flux.merge(
+                messageIdDAO.insert(composedMessageIdWithMetaData)
+                    .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF),
+                indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
+            .then());
+    }
+
     private ComposedMessageIdWithMetaData createMetadataFor(MailboxMessage mailboxMessage) {
         ComposedMessageId composedMessageId = new ComposedMessageId(
             mailboxMessage.getMailboxId(),
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index 64eaf35..7d189c2 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -271,8 +271,6 @@ public class CassandraMessageMapper implements MessageMapper {
         return block(addUidAndModseq(message, mailboxId)
             .flatMap(Throwing.function(messageWithUidAndModSeq -> save(mailbox, messageWithUidAndModSeq)
                 .thenReturn(messageWithUidAndModSeq)))
-            .flatMap(messageWithUidAndModSeq -> indexTableHandler.updateIndexOnAdd(message, mailboxId)
-                .thenReturn(messageWithUidAndModSeq))
             .map(MailboxMessage::metaData));
     }
 
@@ -393,8 +391,6 @@ public class CassandraMessageMapper implements MessageMapper {
         return block(addUidAndModseq(message, mailboxId)
             .flatMap(messageWithUidAndModseq -> insertIds(messageWithUidAndModseq, mailboxId)
                 .thenReturn(messageWithUidAndModseq))
-            .flatMap(messageWithUidAndModseq -> indexTableHandler.updateIndexOnAdd(message, mailboxId)
-                .thenReturn(messageWithUidAndModseq))
             .map(MailboxMessage::metaData));
     }
 
@@ -411,8 +407,11 @@ public class CassandraMessageMapper implements MessageMapper {
                 .modSeq(message.getModSeq())
                 .build();
         return imapUidDAO.insert(composedMessageIdWithMetaData)
-            .then(messageIdDAO.insert(composedMessageIdWithMetaData)
-                .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF));
+            .then(Flux.merge(
+                messageIdDAO.insert(composedMessageIdWithMetaData)
+                    .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF),
+                indexTableHandler.updateIndexOnAdd(message, mailboxId))
+            .then());
     }
 
 


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


[james-project] 08/15: JAMES-3092 Implement Version, Verb and Endpoint

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 43bfc1d60716dedc2c884f60c402bdb023dc8c0d
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Mar 17 14:47:15 2020 +0700

    JAMES-3092 Implement Version, Verb and Endpoint
---
 .../main/java/org/apache/james/jmap/Endpoint.java  | 56 +++++++++++++++++
 .../src/main/java/org/apache/james/jmap/Verb.java  | 27 +++++++++
 .../main/java/org/apache/james/jmap/Version.java   | 70 ++++++++++++++++++++++
 .../java/org/apache/james/jmap/EndpointTest.java   | 33 ++++++++++
 .../java/org/apache/james/jmap/VersionTest.java    | 64 ++++++++++++++++++++
 5 files changed, 250 insertions(+)

diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
new file mode 100644
index 0000000..1c4680c
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
@@ -0,0 +1,56 @@
+/****************************************************************
+ * 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;
+
+import java.util.Objects;
+
+public class Endpoint {
+    private final Verb verb;
+    private final String path;
+
+    public Endpoint(Verb verb, String path) {
+        this.verb = verb;
+        this.path = path;
+    }
+
+    public Verb getVerb() {
+        return verb;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof Endpoint) {
+            Endpoint endpoint = (Endpoint) o;
+
+            return Objects.equals(this.verb, endpoint.verb)
+                && Objects.equals(this.path, endpoint.path);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(verb, path);
+    }
+}
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
new file mode 100644
index 0000000..f37ea9a
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
@@ -0,0 +1,27 @@
+/****************************************************************
+ * 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;
+
+public enum Verb {
+    GET,
+    POST,
+    DELETE,
+    OPTIONS
+}
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java
new file mode 100644
index 0000000..61fd102
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * 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;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+public class Version {
+    public static final Version DRAFT = new Version("draft");
+    public static final Version RFC8621 = new Version("rfc-8621");
+
+    private static final List<Version> AVAILABLE_VERSIONS = ImmutableList.of(
+        DRAFT,
+        RFC8621
+    );
+
+    public static Version of(String version) {
+        Preconditions.checkNotNull(version);
+
+        return AVAILABLE_VERSIONS.stream()
+            .filter(jmapVersion -> jmapVersion.version.equalsIgnoreCase(version))
+            .findFirst()
+            .orElseThrow(() -> new IllegalArgumentException(version + " is not a supported version"));
+    }
+
+    private final String version;
+
+    Version(String version) {
+        this.version = version;
+    }
+
+    public String asString() {
+        return version;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof Version) {
+            Version version = (Version) o;
+
+            return Objects.equals(this.version, version.version);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(version);
+    }
+}
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/EndpointTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/EndpointTest.java
new file mode 100644
index 0000000..1ce9b1d
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/EndpointTest.java
@@ -0,0 +1,33 @@
+/****************************************************************
+ * 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;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class EndpointTest {
+    @Test
+    void shouldRespectBeanContract() {
+        EqualsVerifier.forClass(Endpoint.class)
+            .withIgnoredFields("uriPathTemplate")
+            .verify();
+    }
+}
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
new file mode 100644
index 0000000..c94a8f7
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class VersionTest {
+    @Test
+    void shouldRespectBeanContract() {
+        EqualsVerifier.forClass(Version.class)
+            .verify();
+    }
+
+    @Test
+    void ofShouldReturnCorrectValue() {
+        String version = "rfc-8621";
+
+        assertThat(Version.of(version)).isEqualTo(Version.RFC8621);
+    }
+
+    @Test
+    void ofShouldThrowWhenVersionNotKnown() {
+        String version = "unknown";
+
+        assertThatThrownBy(() -> Version.of(version))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowWhenVersionIsNull() {
+        assertThatThrownBy(() -> Version.of(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void ofShouldThrowWhenVersionIsEmpty() {
+        String version = "";
+
+        assertThatThrownBy(() -> Version.of(version))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+}


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


[james-project] 03/15: JAMES-3136 Limit message projection inconsistency with a retry strategy

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0269543e57183bf53e0f4cf75faa6c2494ee6f1d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 10:35:50 2020 +0700

    JAMES-3136 Limit message projection inconsistency with a retry strategy
---
 .../mailbox/cassandra/mail/CassandraMessageMapper.java    |  8 +++++++-
 .../cassandra/mail/CassandraMessageMapperTest.java        | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index 807b891..64eaf35 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.time.Duration;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -66,6 +67,10 @@ import reactor.core.scheduler.Schedulers;
 public class CassandraMessageMapper implements MessageMapper {
     public static final Logger LOGGER = LoggerFactory.getLogger(CassandraMessageMapper.class);
 
+    private static final int MAX_RETRY = 5;
+    private static final Duration MIN_RETRY_BACKOFF = Duration.ofMillis(10);
+    private static final Duration MAX_RETRY_BACKOFF = Duration.ofMillis(1000);
+
     private final CassandraModSeqProvider modSeqProvider;
     private final CassandraUidProvider uidProvider;
     private final CassandraMessageDAO messageDAO;
@@ -406,7 +411,8 @@ public class CassandraMessageMapper implements MessageMapper {
                 .modSeq(message.getModSeq())
                 .build();
         return imapUidDAO.insert(composedMessageIdWithMetaData)
-            .then(messageIdDAO.insert(composedMessageIdWithMetaData));
+            .then(messageIdDAO.insert(composedMessageIdWithMetaData)
+                .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF));
     }
 
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
index fb647c0..7898cb4 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.mailbox.cassandra.mail;
 
 import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Optional;
 
@@ -164,5 +165,19 @@ class CassandraMessageMapperTest extends MessageMapperTest {
                     .hasSize(1);
             }));
         }
+
+        @Test
+        void addShouldRetryMessageDenormalization(CassandraCluster cassandra) throws Exception {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .times(5)
+                    .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            messageMapper.add(benwaInboxMailbox, message1);
+
+            assertThat(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.all(), MessageMapper.FetchType.Metadata, 1))
+                .toIterable()
+                .hasSize(1);
+        }
     }
 }


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


[james-project] 13/15: JAMES-3092 Using HttpCore library to parse correctly the version parameter in accept header

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1c3b7f550c1082c077801daf0399d5b99d8c5e5f
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Apr 1 10:34:29 2020 +0700

    JAMES-3092 Using HttpCore library to parse correctly the version parameter in accept header
---
 server/protocols/jmap/pom.xml                      |  5 ++++
 .../org/apache/james/jmap/JMAPRoutesHandler.java   | 27 ++++++++++++++--------
 2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index 1da1e9e..e7b7e0b 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -65,6 +65,11 @@
             <artifactId>javax.annotation-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4.13</version>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
             <scope>test</scope>
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
index 8c094f5..2edb6cc 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
@@ -22,16 +22,18 @@ package org.apache.james.jmap;
 import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
 
 import java.util.Arrays;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicHeaderValueParser;
+
 import com.google.common.collect.ImmutableSet;
 
 import reactor.netty.http.server.HttpServerRequest;
 
 public class JMAPRoutesHandler {
-    String JMAP_VERSION_HEADER = "jmapVersion=";
+    private static String JMAP_VERSION_HEADER = "jmapVersion";
 
     private final Version version;
     private final Set<JMAPRoutes> routes;
@@ -54,15 +56,22 @@ public class JMAPRoutesHandler {
     }
 
     private Version extractRequestVersionHeader(HttpServerRequest request) {
-        return Optional.ofNullable(request.requestHeaders().get(ACCEPT))
-            .map(s -> s.split(";"))
-            .map(Arrays::stream)
-            .orElse(Stream.of())
-            .map(value -> value.trim().toLowerCase())
-            .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
-            .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
+        return asVersion(request)
+            .filter(nameValuePair -> nameValuePair.getName().equals(JMAP_VERSION_HEADER))
+            .map(NameValuePair::getValue)
             .map(Version::of)
             .findFirst()
             .orElse(Version.DRAFT);
     }
+
+    private Stream<NameValuePair> asVersion(HttpServerRequest request) {
+        return request.requestHeaders()
+            .getAll(ACCEPT)
+            .stream()
+            .flatMap(this::extractValueParameters);
+    }
+
+    private Stream<NameValuePair> extractValueParameters(String value) {
+        return Arrays.stream(BasicHeaderValueParser.parseParameters(value, BasicHeaderValueParser.INSTANCE));
+    }
 }


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


[james-project] 09/15: JAMES-3092 Instauring the Y structure with jmap-draft

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6431f55128e974f51826d58899e18a65adbc0f19
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Mar 18 17:44:49 2020 +0700

    JAMES-3092 Instauring the Y structure with jmap-draft
---
 .../james/jmap/http/AuthenticationRoutes.java      |  19 ++-
 .../org/apache/james/jmap/http/DownloadRoutes.java |  22 +--
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  |  14 +-
 .../org/apache/james/jmap/http/UploadRoutes.java   |  14 +-
 .../apache/james/jmap/http/JMAPApiRoutesTest.java  |   9 +-
 server/protocols/jmap/pom.xml                      |   4 +
 .../main/java/org/apache/james/jmap/Endpoint.java  |   6 +
 .../james/jmap/{Endpoint.java => JMAPRoute.java}   |  45 +++----
 .../java/org/apache/james/jmap/JMAPRoutes.java     |  11 +-
 .../java/org/apache/james/jmap/JMAPServer.java     |  77 ++++++++++-
 .../src/main/java/org/apache/james/jmap/Verb.java  |  19 ++-
 .../java/org/apache/james/jmap/JMAPServerTest.java | 150 +++++++++++++++++++++
 12 files changed, 334 insertions(+), 56 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index 4c33bc9..ed06cbf 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -37,12 +37,17 @@ import static org.apache.james.util.ReactorUtils.logOnError;
 
 import java.io.IOException;
 import java.util.Objects;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
 import org.apache.james.core.Username;
+import org.apache.james.jmap.Endpoint;
+import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.JMAPUrls;
+import org.apache.james.jmap.Verb;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.draft.api.AccessTokenManager;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
@@ -69,7 +74,6 @@ import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public class AuthenticationRoutes implements JMAPRoutes {
     private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationRoutes.class);
@@ -102,12 +106,13 @@ public class AuthenticationRoutes implements JMAPRoutes {
     }
 
     @Override
-    public HttpServerRoutes define(HttpServerRoutes builder) {
-        return builder
-            .post(AUTHENTICATION, JMAPRoutes.corsHeaders(this::post))
-            .get(AUTHENTICATION, JMAPRoutes.corsHeaders(this::returnEndPointsResponse))
-            .delete(AUTHENTICATION, JMAPRoutes.corsHeaders(this::delete))
-            .options(AUTHENTICATION, CORS_CONTROL);
+    public Stream<JMAPRoute> routes() {
+        return Stream.of(
+            new JMAPRoute(new Endpoint(Verb.POST, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(Verb.GET, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
+            new JMAPRoute(new Endpoint(Verb.DELETE, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::delete)),
+            new JMAPRoute(new Endpoint(Verb.OPTIONS, AUTHENTICATION), Version.DRAFT, CORS_CONTROL)
+        );
     }
 
     private Mono<Void> post(HttpServerRequest request, HttpServerResponse response) {
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index c32edc6..0f97b15 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -33,10 +33,15 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import org.apache.james.jmap.Endpoint;
+import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
+import org.apache.james.jmap.Verb;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
@@ -65,7 +70,6 @@ import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public class DownloadRoutes implements JMAPRoutes {
     private static final Logger LOGGER = LoggerFactory.getLogger(DownloadRoutes.class);
@@ -95,13 +99,15 @@ public class DownloadRoutes implements JMAPRoutes {
     }
 
     @Override
-    public HttpServerRoutes define(HttpServerRoutes builder) {
-        return builder.post(DOWNLOAD_FROM_ID, JMAPRoutes.corsHeaders(this::postFromId))
-            .get(DOWNLOAD_FROM_ID, JMAPRoutes.corsHeaders(this::getFromId))
-            .post(DOWNLOAD_FROM_ID_AND_NAME, JMAPRoutes.corsHeaders(this::postFromIdAndName))
-            .get(DOWNLOAD_FROM_ID_AND_NAME, JMAPRoutes.corsHeaders(this::getFromIdAndName))
-            .options(DOWNLOAD_FROM_ID, CORS_CONTROL)
-            .options(DOWNLOAD_FROM_ID_AND_NAME, CORS_CONTROL);
+    public Stream<JMAPRoute> routes() {
+        return Stream.of(
+            new JMAPRoute(new Endpoint(Verb.POST, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromId)),
+            new JMAPRoute(new Endpoint(Verb.GET, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromId)),
+            new JMAPRoute(new Endpoint(Verb.POST, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromIdAndName)),
+            new JMAPRoute(new Endpoint(Verb.GET, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromIdAndName)),
+            new JMAPRoute(new Endpoint(Verb.OPTIONS, DOWNLOAD_FROM_ID), Version.DRAFT, CORS_CONTROL),
+            new JMAPRoute(new Endpoint(Verb.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, CORS_CONTROL)
+        );
     }
 
     private Mono<Void> postFromId(HttpServerRequest request, HttpServerResponse response) {
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index f8dc7ca..82b8669 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -27,10 +27,15 @@ import static org.apache.james.jmap.http.LoggingHelper.jmapContext;
 import static org.apache.james.util.ReactorUtils.logOnError;
 
 import java.io.IOException;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import org.apache.james.jmap.Endpoint;
+import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
+import org.apache.james.jmap.Verb;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
 import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
@@ -53,7 +58,6 @@ import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public class JMAPApiRoutes implements JMAPRoutes {
     public static final Logger LOGGER = LoggerFactory.getLogger(JMAPApiRoutes.class);
@@ -82,9 +86,11 @@ public class JMAPApiRoutes implements JMAPRoutes {
     }
 
     @Override
-    public HttpServerRoutes define(HttpServerRoutes builder) {
-        return builder.post(JMAP, JMAPRoutes.corsHeaders(this::post))
-            .options(JMAP, CORS_CONTROL);
+    public Stream<JMAPRoute> routes() {
+        return Stream.of(
+            new JMAPRoute(new Endpoint(Verb.POST, JMAP), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(Verb.OPTIONS, JMAP), Version.DRAFT, CORS_CONTROL)
+        );
     }
 
     private Mono<Void> post(HttpServerRequest request, HttpServerResponse response) {
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index 2465b0b..253a076 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -31,10 +31,15 @@ import static org.apache.james.util.ReactorUtils.logOnError;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import org.apache.james.jmap.Endpoint;
+import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
+import org.apache.james.jmap.Verb;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
 import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
@@ -56,7 +61,6 @@ import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public class UploadRoutes implements JMAPRoutes {
     private static final Logger LOGGER = LoggerFactory.getLogger(UploadRoutes.class);
@@ -84,9 +88,11 @@ public class UploadRoutes implements JMAPRoutes {
     }
 
     @Override
-    public HttpServerRoutes define(HttpServerRoutes builder) {
-        return builder.post(UPLOAD, JMAPRoutes.corsHeaders(this::post))
-            .options(UPLOAD, CORS_CONTROL);
+    public Stream<JMAPRoute> routes() {
+        return Stream.of(
+            new JMAPRoute(new Endpoint(Verb.POST, UPLOAD), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(Verb.OPTIONS, UPLOAD), Version.DRAFT, CORS_CONTROL)
+        );
     }
 
     private Mono<Void> post(HttpServerRequest request, HttpServerResponse response)  {
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
index 7973c98..f688099 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
@@ -31,6 +31,8 @@ import static org.mockito.Mockito.when;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.james.core.Username;
+import org.apache.james.jmap.JMAPRoute;
+import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.draft.methods.ErrorResponse;
 import org.apache.james.jmap.draft.methods.Method;
 import org.apache.james.jmap.draft.methods.RequestHandler;
@@ -73,9 +75,14 @@ public class JMAPApiRoutesTest {
         JMAPApiRoutes jmapApiRoutes = new JMAPApiRoutes(requestHandler, new RecordingMetricFactory(),
             mockedAuthFilter, mockedUserProvisionner, mockedMailboxesProvisionner);
 
+        JMAPRoute postApiRoute = jmapApiRoutes.routes()
+            .filter(jmapRoute -> jmapRoute.getEndpoint().getVerb().equals(Verb.POST))
+            .findFirst()
+            .get();
+
         server = HttpServer.create()
             .port(RANDOM_PORT)
-            .route(jmapApiRoutes::define)
+            .route(routes -> routes.post(postApiRoute.getEndpoint().getPath(), (req, res) -> postApiRoute.getAction().apply(req, res)))
             .bindNow();
 
         RestAssured.requestSpecification = new RequestSpecBuilder()
diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index 7b40e91..1da1e9e 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -61,6 +61,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
             <scope>test</scope>
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
index 1c4680c..3c878df 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
@@ -21,6 +21,8 @@ package org.apache.james.jmap;
 
 import java.util.Objects;
 
+import reactor.netty.http.server.HttpServerRoutes;
+
 public class Endpoint {
     private final Verb verb;
     private final String path;
@@ -38,6 +40,10 @@ public class Endpoint {
         return path;
     }
 
+    HttpServerRoutes registerRoute(HttpServerRoutes builder, JMAPRoute.Action action) {
+        return verb.registerRoute(builder, this.path, action);
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof Endpoint) {
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
similarity index 62%
copy from server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
copy to server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
index 1c4680c..d4d8986 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
@@ -19,38 +19,37 @@
 
 package org.apache.james.jmap;
 
-import java.util.Objects;
+import java.util.function.BiFunction;
 
-public class Endpoint {
-    private final Verb verb;
-    private final String path;
+import org.reactivestreams.Publisher;
 
-    public Endpoint(Verb verb, String path) {
-        this.verb = verb;
-        this.path = path;
-    }
+import reactor.netty.http.server.HttpServerRequest;
+import reactor.netty.http.server.HttpServerResponse;
+
+public class JMAPRoute {
+    public interface Action extends BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> {
 
-    public Verb getVerb() {
-        return verb;
     }
 
-    public String getPath() {
-        return path;
+    private final Endpoint endpoint;
+    private final Version version;
+    private final Action action;
+
+    public JMAPRoute(Endpoint endpoint, Version version, Action action) {
+        this.endpoint = endpoint;
+        this.version = version;
+        this.action = action;
     }
 
-    @Override
-    public final boolean equals(Object o) {
-        if (o instanceof Endpoint) {
-            Endpoint endpoint = (Endpoint) o;
+    public Endpoint getEndpoint() {
+        return endpoint;
+    }
 
-            return Objects.equals(this.verb, endpoint.verb)
-                && Objects.equals(this.path, endpoint.path);
-        }
-        return false;
+    public Version getVersion() {
+        return version;
     }
 
-    @Override
-    public final int hashCode() {
-        return Objects.hash(verb, path);
+    public Action getAction() {
+        return action;
     }
 }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
index c42085d..779c8ca 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
@@ -23,22 +23,19 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
 import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
 
-import java.util.function.BiFunction;
+import java.util.stream.Stream;
 
-import org.reactivestreams.Publisher;
 import org.slf4j.Logger;
 
 import reactor.core.publisher.Mono;
-import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public interface JMAPRoutes {
-    HttpServerRoutes define(HttpServerRoutes builder);
+    Stream<JMAPRoute> routes();
 
-    BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> CORS_CONTROL = corsHeaders((req, res) -> res.send());
+    JMAPRoute.Action CORS_CONTROL = corsHeaders((req, res) -> res.send());
 
-    static BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> corsHeaders(BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> action) {
+    static JMAPRoute.Action corsHeaders(JMAPRoute.Action action) {
         return (req, res) -> action.apply(req, res
             .header("Access-Control-Allow-Origin", "*")
             .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index 3b8bbbd..12426ed 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -19,6 +19,13 @@
 
 package org.apache.james.jmap;
 
+import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
 import java.util.Optional;
 import java.util.Set;
 
@@ -29,11 +36,18 @@ import org.apache.james.lifecycle.api.Startable;
 import org.apache.james.util.Port;
 import org.slf4j.LoggerFactory;
 
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+
 import reactor.netty.DisposableServer;
 import reactor.netty.http.server.HttpServer;
+import reactor.netty.http.server.HttpServerRequest;
+import reactor.netty.http.server.HttpServerRoutes;
 
 public class JMAPServer implements Startable {
     private static final int RANDOM_PORT = 0;
+    private static final String JMAP_VERSION_HEADER = "jmapVersion=";
 
     private final JMAPConfiguration configuration;
     private final Set<JMAPRoutes> jmapRoutes;
@@ -53,12 +67,17 @@ public class JMAPServer implements Startable {
     }
 
     public void start() {
+        ImmutableListMultimap<Endpoint, JMAPRoute> collect = jmapRoutes.stream()
+            .flatMap(JMAPRoutes::routes)
+            .collect(Guavate.toImmutableListMultimap(JMAPRoute::getEndpoint));
+
         if (configuration.isEnabled()) {
             server = Optional.of(HttpServer.create()
                 .port(configuration.getPort()
                     .map(Port::getValue)
                     .orElse(RANDOM_PORT))
-                .route(routes -> jmapRoutes.forEach(jmapRoute -> jmapRoute.define(routes)))
+                .route(routes -> jmapRoutes.forEach(jmapRoute -> collect.asMap().forEach(
+                    (endpoint, route) -> injectRoutes(routes, endpoint, route))))
                 .wiretap(wireTapEnabled())
                 .bindNow());
         }
@@ -68,6 +87,62 @@ public class JMAPServer implements Startable {
         return LoggerFactory.getLogger("org.apache.james.jmap.wire").isTraceEnabled();
     }
 
+    private HttpServerRoutes injectRoutes(HttpServerRoutes builder, Endpoint endpoint, Collection<JMAPRoute> routesList) {
+        if (routesList.size() == 1) {
+            JMAPRoute next = routesList.iterator().next();
+
+            return endpoint.registerRoute(builder, (req, res) ->
+                getExistingRoute(extractRequestVersionHeader(req), next).apply(req, res));
+        } else if (routesList.size() == 2) {
+            ImmutableList<JMAPRoute> sorted = routesList.stream()
+                .sorted(Comparator.comparing(JMAPRoute::getVersion))
+                .collect(Guavate.toImmutableList());
+            JMAPRoute draftRoute = sorted.get(0);
+            JMAPRoute rfc8621Route = sorted.get(1);
+
+            return endpoint.registerRoute(builder, (req, res) ->
+                chooseVersionRoute(extractRequestVersionHeader(req), draftRoute, rfc8621Route).apply(req, res));
+        }
+        return builder;
+    }
+
+    private JMAPRoute.Action getExistingRoute(String version, JMAPRoute route) {
+        try {
+            if (Version.of(version).equals(route.getVersion())) {
+                return route.getAction();
+            }
+        } catch (IllegalArgumentException e) {
+            return (req, res) -> res.status(BAD_REQUEST).send();
+        }
+        return (req, res) -> res.status(NOT_FOUND).send();
+    }
+
+    private JMAPRoute.Action chooseVersionRoute(String version, JMAPRoute draftRoute, JMAPRoute rfc8621Route) {
+        try {
+            if (hasRfc8621AcceptHeader(version)) {
+                return rfc8621Route.getAction();
+            }
+        } catch (IllegalArgumentException e) {
+            return (req, res) -> res.status(BAD_REQUEST).send();
+        }
+        return draftRoute.getAction();
+    }
+
+    private boolean hasRfc8621AcceptHeader(String version) {
+        return Version.of(version).equals(Version.RFC8621);
+    }
+
+    private String extractRequestVersionHeader(HttpServerRequest request) {
+        return Arrays.stream(request.requestHeaders()
+                .get(ACCEPT)
+                .split(";"))
+            .map(value -> value.trim().toLowerCase())
+            .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
+            .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
+            .findFirst()
+            .orElse(Version.DRAFT.getVersion());
+    }
+
     @PreDestroy
     public void stop() {
         server.ifPresent(DisposableServer::disposeNow);
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
index f37ea9a..7a94547 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
@@ -19,9 +19,26 @@
 
 package org.apache.james.jmap;
 
+import reactor.netty.http.server.HttpServerRoutes;
+
 public enum Verb {
     GET,
     POST,
     DELETE,
-    OPTIONS
+    OPTIONS;
+
+    HttpServerRoutes registerRoute(HttpServerRoutes builder, String path, JMAPRoute.Action action) {
+        switch (this) {
+            case GET:
+                return builder.get(path, action);
+            case POST:
+                return builder.post(path, action);
+            case DELETE:
+                return builder.delete(path, action);
+            case OPTIONS:
+                return builder.options(path, action);
+            default:
+                return builder;
+        }
+    }
 }
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index 1c36384..b03a836 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -19,15 +19,41 @@
 
 package org.apache.james.jmap;
 
+import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.restassured.RestAssured.given;
+import static io.restassured.config.EncoderConfig.encoderConfig;
+import static io.restassured.config.RestAssuredConfig.newConfig;
+import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.Matchers.is;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.http.ContentType;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.server.HttpServerResponse;
+
 class JMAPServerTest {
+    private static final String ACCEPT_JMAP_VERSION_HEADER = "application/json; jmapVersion=";
+    private static final String ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.asString();
+    private static final String ACCEPT_RFC8621_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.RFC8621.asString();
+
     private static final JMAPConfiguration DISABLED_CONFIGURATION = JMAPConfiguration.builder().disable().build();
     private static final JMAPConfiguration TEST_CONFIGURATION = JMAPConfiguration.builder()
         .enable()
@@ -35,6 +61,20 @@ class JMAPServerTest {
         .build();
     private static final ImmutableSet<JMAPRoutes> NO_ROUTES = ImmutableSet.of();
 
+    private static final ImmutableSet<Endpoint> AUTHENTICATION_ENDPOINTS = ImmutableSet.of(
+        new Endpoint(Verb.POST, JMAPUrls.AUTHENTICATION),
+        new Endpoint(Verb.GET, JMAPUrls.AUTHENTICATION)
+    );
+    private static final ImmutableSet<Endpoint> JMAP_ENDPOINTS = ImmutableSet.of(
+        new Endpoint(Verb.POST, JMAPUrls.JMAP),
+        new Endpoint(Verb.DELETE, JMAPUrls.JMAP)
+    );
+    private static final ImmutableSet<JMAPRoutes> FAKE_ROUTES = ImmutableSet.of(
+        new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.DRAFT),
+        new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.RFC8621),
+        new FakeJMAPRoutes(JMAP_ENDPOINTS, Version.DRAFT)
+    );
+
     @Test
     void serverShouldAnswerWhenStarted() {
         JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES);
@@ -84,4 +124,114 @@ class JMAPServerTest {
         assertThatThrownBy(jmapServer::getPort)
             .isInstanceOf(IllegalStateException.class);
     }
+
+    @Nested
+    class RouteVersioningTest {
+        JMAPServer server;
+
+        @BeforeEach
+        void setUp() {
+            server = new JMAPServer(TEST_CONFIGURATION, FAKE_ROUTES);
+            server.start();
+
+            RestAssured.requestSpecification = new RequestSpecBuilder()
+                .setContentType(ContentType.JSON)
+                .setAccept(ContentType.JSON)
+                .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8)))
+                .setPort(server.getPort().getValue())
+                .build();
+        }
+
+        @AfterEach
+        void tearDown() {
+            server.stop();
+        }
+
+        @Test
+        void serverShouldReturnDefaultVersionRouteWhenNoVersionHeader() {
+            given()
+                .basePath(JMAPUrls.AUTHENTICATION)
+            .when()
+                .get()
+            .then()
+                .statusCode(HttpResponseStatus.OK.code())
+                .body("Version", is(Version.DRAFT.asString()));
+        }
+
+        @Test
+        void serverShouldReturnCorrectRouteWhenTwoVersionRoutes() {
+            given()
+                .basePath(JMAPUrls.AUTHENTICATION)
+                .header(ACCEPT.toString(), ACCEPT_RFC8621_VERSION_HEADER)
+            .when()
+                .get()
+            .then()
+                .statusCode(HttpResponseStatus.OK.code())
+                .body("Version", is(Version.RFC8621.asString()));
+        }
+
+        @Test
+        void serverShouldReturnCorrectRouteWhenOneVersionRoute() {
+            given()
+                .basePath(JMAPUrls.JMAP)
+                .header(ACCEPT.toString(), ACCEPT_DRAFT_VERSION_HEADER)
+            .when()
+                .post()
+            .then()
+                .statusCode(HttpResponseStatus.OK.code())
+                .body("Version", is(Version.DRAFT.asString()));
+        }
+
+        @Test
+        void serverShouldReturnNotFoundWhenRouteVersionDoesNotExist() {
+            given()
+                .basePath(JMAPUrls.JMAP)
+                .header(ACCEPT.toString(), ACCEPT_RFC8621_VERSION_HEADER)
+            .when()
+                .post()
+            .then()
+                .statusCode(HttpResponseStatus.NOT_FOUND.code());
+        }
+
+        @Test
+        void serverShouldReturnBadRequestWhenVersionIsUnknown() {
+            given()
+                .basePath(JMAPUrls.AUTHENTICATION)
+                .header(ACCEPT.toString(), ACCEPT_JMAP_VERSION_HEADER + "unknown")
+            .when()
+                .get()
+            .then()
+                .statusCode(HttpResponseStatus.BAD_REQUEST.code());
+        }
+    }
+
+    private static class FakeJMAPRoutes implements JMAPRoutes {
+        private static final Logger LOGGER = LoggerFactory.getLogger(FakeJMAPRoutes.class);
+
+        private final Set<Endpoint> endpoints;
+        private final Version version;
+
+        private FakeJMAPRoutes(Set<Endpoint> endpoints, Version version) {
+            this.endpoints = endpoints;
+            this.version = version;
+        }
+
+        @Override
+        public Stream<JMAPRoute> routes() {
+            return endpoints.stream()
+                .map(endpoint -> new JMAPRoute(endpoint, version, (request, response) -> sendVersionResponse(response)));
+        }
+
+        @Override
+        public Logger logger() {
+            return LOGGER;
+        }
+
+        private Mono<Void> sendVersionResponse(HttpServerResponse response) {
+            return response.status(HttpResponseStatus.OK)
+                .header(CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8)
+                .sendString(Mono.just(String.format("{\"Version\":\"%s\"}", version.asString())))
+                .then();
+        }
+    }
 }


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


[james-project] 04/15: JAMES-3136 Failure tests for CassandraMessageIdMapper

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 52d55c59941ae3a1256a088009a6c1b9477ae6ca
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 11:34:31 2020 +0700

    JAMES-3136 Failure tests for CassandraMessageIdMapper
---
 .../cassandra/mail/CassandraMessageIdMapper.java   |   5 +-
 .../mail/CassandraMessageIdMapperTest.java         | 132 +++++++++++++++++++++
 .../store/mail/model/MessageIdMapperTest.java      |   6 +-
 3 files changed, 136 insertions(+), 7 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index 0eeca22..4d88c9a 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -127,9 +127,8 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         mailboxMapper.findMailboxById(mailboxId);
         ComposedMessageIdWithMetaData composedMessageIdWithMetaData = createMetadataFor(mailboxMessage);
         messageDAO.save(mailboxMessage)
-            .thenMany(Flux.merge(
-                imapUidDAO.insert(composedMessageIdWithMetaData),
-                messageIdDAO.insert(composedMessageIdWithMetaData)))
+            .thenEmpty(imapUidDAO.insert(composedMessageIdWithMetaData))
+            .thenEmpty(messageIdDAO.insert(composedMessageIdWithMetaData))
             .thenEmpty(indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
             .block();
     }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
index 98ef169..582569a 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
@@ -19,10 +19,13 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.List;
+import java.util.Optional;
 
+import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.core.Username;
@@ -30,13 +33,18 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MailboxSessionUtil;
 import org.apache.james.mailbox.cassandra.CassandraMailboxSessionMapperFactory;
 import org.apache.james.mailbox.cassandra.TestCassandraMailboxSessionMapperFactory;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.collect.ImmutableList;
 
 class CassandraMessageIdMapperTest extends MessageIdMapperTest {
@@ -73,4 +81,128 @@ class CassandraMessageIdMapperTest extends MessageIdMapperTest {
         assertThat(messages)
             .containsOnly(message1, message2, message3, message4);
     }
+
+    @Nested
+    class FailureTest {
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailToPersistInMessageDAO(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO messageV2 (messageId,internalDate,bodyStartOctet,fullContentOctets,bodyOctets,bodyContent,headerContent,properties,textualLineCount,attachments)"));
+
+            try {
+                message1.setUid(mapperProvider.generateMessageUid());
+                message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+                sut.save(message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistBlobParts(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO blobParts (id,chunkNumber,data) VALUES (:id,:chunkNumber,:data);"));
+
+            try {
+                message1.setUid(mapperProvider.generateMessageUid());
+                message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+                sut.save(message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistBlobs(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO blobs (id,position) VALUES (:id,:position);"));
+
+            try {
+                message1.setUid(mapperProvider.generateMessageUid());
+                message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+                sut.save(message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void retrieveMessagesShouldNotReturnMessagesWhenFailsToPersistInImapUidTable(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO imapUidTable (messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            try {
+                message1.setUid(mapperProvider.generateMessageUid());
+                message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+                sut.save(message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdDAO messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveMessages((CassandraId) benwaInboxMailbox.getMailboxId(), MessageRange.all()).collectList().block())
+                    .isEmpty();
+            }));
+        }
+
+        @Test
+        void addShouldPersistInTableOfTruthWhenMessageIdTableWritesFails(CassandraCluster cassandra) {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .forever()
+                    .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            try {
+                message1.setUid(mapperProvider.generateMessageUid());
+                message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+                sut.save(message1);
+            } catch (Exception e) {
+                // ignoring expected error
+            }
+
+            CassandraMessageIdToImapUidDAO imapUidDAO = new CassandraMessageIdToImapUidDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+
+            SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
+                softly.assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                    .hasSize(1);
+                softly.assertThat(imapUidDAO.retrieve((CassandraMessageId) message1.getMessageId(), Optional.empty()).collectList().block())
+                    .hasSize(1);
+            }));
+        }
+    }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
index 0c9fc61..57b90f9 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
@@ -25,7 +25,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.time.Duration;
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
 
 import javax.mail.Flags;
 import javax.mail.Flags.Flag;
@@ -42,7 +41,6 @@ import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageId;
-import org.apache.james.mailbox.model.MessageMetaData;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.mailbox.model.UpdatedFlags;
 import org.apache.james.mailbox.store.mail.MailboxMapper;
@@ -71,7 +69,7 @@ public abstract class MessageIdMapperTest {
 
     private MessageMapper messageMapper;
     private MailboxMapper mailboxMapper;
-    private MessageIdMapper sut;
+    protected MessageIdMapper sut;
 
     protected Mailbox benwaInboxMailbox;
     private Mailbox benwaWorkMailbox;
@@ -81,7 +79,7 @@ public abstract class MessageIdMapperTest {
     protected SimpleMailboxMessage message3;
     protected SimpleMailboxMessage message4;
 
-    private MapperProvider mapperProvider;
+    protected MapperProvider mapperProvider;
 
     protected abstract MapperProvider provideMapper();
 


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


[james-project] 15/15: JAMES-3092 Implement VersionParser to parse the version header against an injected set of supported versions

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f6ec49ad12ee3aa805c2a8371bc449ab82805ba0
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Apr 6 11:39:37 2020 +0700

    JAMES-3092 Implement VersionParser to parse the version header against an injected set of supported versions
---
 .../org/apache/james/jmap/draft/JMAPModule.java    |  4 +++
 .../org/apache/james/jmap/JMAPRoutesHandler.java   | 38 ++--------------------
 .../java/org/apache/james/jmap/JMAPServer.java     |  6 ++--
 .../main/java/org/apache/james/jmap/Version.java   | 18 ----------
 .../{JMAPRoutesHandler.java => VersionParser.java} | 38 +++++++++++-----------
 .../java/org/apache/james/jmap/JMAPServerTest.java | 22 +++++++++----
 .../{VersionTest.java => VersionParserTest.java}   | 35 ++++++++++++--------
 .../java/org/apache/james/jmap/VersionTest.java    | 32 ------------------
 8 files changed, 66 insertions(+), 127 deletions(-)

diff --git a/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/JMAPModule.java b/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
index b00e73a..850f24e 100644
--- a/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
+++ b/server/container/guice/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
@@ -31,6 +31,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.JMAPConfiguration;
 import org.apache.james.jmap.JMAPServer;
+import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.methods.RequestHandler;
 import org.apache.james.jmap.draft.send.PostDequeueDecoratorFactory;
 import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
@@ -105,6 +106,9 @@ public class JMAPModule extends AbstractModule {
         bind(MailQueueItemDecoratorFactory.class).to(PostDequeueDecoratorFactory.class).in(Scopes.SINGLETON);
 
         Multibinder.newSetBinder(binder(), MailboxListener.GroupMailboxListener.class).addBinding().to(PropagateLookupRightListener.class);
+
+        Multibinder<Version> supportedVersions = Multibinder.newSetBinder(binder(), Version.class);
+        supportedVersions.addBinding().toInstance(Version.DRAFT);
     }
 
     @Provides
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
index 2edb6cc..498b778 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
@@ -19,22 +19,12 @@
 
 package org.apache.james.jmap;
 
-import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
-
-import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Stream;
 
-import org.apache.http.NameValuePair;
-import org.apache.http.message.BasicHeaderValueParser;
-
 import com.google.common.collect.ImmutableSet;
 
-import reactor.netty.http.server.HttpServerRequest;
-
 public class JMAPRoutesHandler {
-    private static String JMAP_VERSION_HEADER = "jmapVersion";
-
     private final Version version;
     private final Set<JMAPRoutes> routes;
 
@@ -43,35 +33,11 @@ public class JMAPRoutesHandler {
         this.routes = ImmutableSet.copyOf(routes);
     }
 
-    Stream<JMAPRoute> routes(HttpServerRequest request) {
-        if (matches(request)) {
+    Stream<JMAPRoute> routes(Version versionRequest) {
+        if (version.equals(versionRequest)) {
             return routes.stream()
                 .flatMap(JMAPRoutes::routes);
         }
         return Stream.of();
     }
-
-    private boolean matches(HttpServerRequest request) {
-        return version.equals(extractRequestVersionHeader(request));
-    }
-
-    private Version extractRequestVersionHeader(HttpServerRequest request) {
-        return asVersion(request)
-            .filter(nameValuePair -> nameValuePair.getName().equals(JMAP_VERSION_HEADER))
-            .map(NameValuePair::getValue)
-            .map(Version::of)
-            .findFirst()
-            .orElse(Version.DRAFT);
-    }
-
-    private Stream<NameValuePair> asVersion(HttpServerRequest request) {
-        return request.requestHeaders()
-            .getAll(ACCEPT)
-            .stream()
-            .flatMap(this::extractValueParameters);
-    }
-
-    private Stream<NameValuePair> extractValueParameters(String value) {
-        return Arrays.stream(BasicHeaderValueParser.parseParameters(value, BasicHeaderValueParser.INSTANCE));
-    }
 }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index 1bbdd24..6e9b290 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -41,12 +41,14 @@ public class JMAPServer implements Startable {
 
     private final JMAPConfiguration configuration;
     private final Set<JMAPRoutesHandler> jmapRoutesHandlers;
+    private final VersionParser versionParser;
     private Optional<DisposableServer> server;
 
     @Inject
-    public JMAPServer(JMAPConfiguration configuration, Set<JMAPRoutesHandler> jmapRoutesHandlers) {
+    public JMAPServer(JMAPConfiguration configuration, Set<JMAPRoutesHandler> jmapRoutesHandlers, VersionParser versionParser) {
         this.configuration = configuration;
         this.jmapRoutesHandlers = jmapRoutesHandlers;
+        this.versionParser = versionParser;
         this.server = Optional.empty();
     }
 
@@ -75,7 +77,7 @@ public class JMAPServer implements Startable {
     private JMAPRoute.Action handleVersionRoute(HttpServerRequest request) {
         try {
             return jmapRoutesHandlers.stream()
-                .flatMap(jmapRoutesHandler -> jmapRoutesHandler.routes(request))
+                .flatMap(jmapRoutesHandler -> jmapRoutesHandler.routes(versionParser.parseRequestVersionHeader(request)))
                 .filter(jmapRoute -> jmapRoute.matches(request))
                 .map(JMAPRoute::getAction)
                 .findFirst()
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java
index 61fd102..7fe2ef5 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Version.java
@@ -19,30 +19,12 @@
 
 package org.apache.james.jmap;
 
-import java.util.List;
 import java.util.Objects;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
 public class Version {
     public static final Version DRAFT = new Version("draft");
     public static final Version RFC8621 = new Version("rfc-8621");
 
-    private static final List<Version> AVAILABLE_VERSIONS = ImmutableList.of(
-        DRAFT,
-        RFC8621
-    );
-
-    public static Version of(String version) {
-        Preconditions.checkNotNull(version);
-
-        return AVAILABLE_VERSIONS.stream()
-            .filter(jmapVersion -> jmapVersion.version.equalsIgnoreCase(version))
-            .findFirst()
-            .orElseThrow(() -> new IllegalArgumentException(version + " is not a supported version"));
-    }
-
     private final String version;
 
     Version(String version) {
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/VersionParser.java
similarity index 71%
copy from server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
copy to server/protocols/jmap/src/main/java/org/apache/james/jmap/VersionParser.java
index 2edb6cc..0368679 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutesHandler.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/VersionParser.java
@@ -25,41 +25,41 @@ import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
+
 import org.apache.http.NameValuePair;
 import org.apache.http.message.BasicHeaderValueParser;
 
-import com.google.common.collect.ImmutableSet;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 
 import reactor.netty.http.server.HttpServerRequest;
 
-public class JMAPRoutesHandler {
-    private static String JMAP_VERSION_HEADER = "jmapVersion";
+public class VersionParser {
+    private static final String JMAP_VERSION_HEADER = "jmapVersion";
 
-    private final Version version;
-    private final Set<JMAPRoutes> routes;
+    private final Set<Version> supportedVersions;
 
-    public JMAPRoutesHandler(Version version, JMAPRoutes... routes) {
-        this.version = version;
-        this.routes = ImmutableSet.copyOf(routes);
+    @Inject
+    public VersionParser(Set<Version> supportedVersions) {
+        this.supportedVersions = supportedVersions;
     }
 
-    Stream<JMAPRoute> routes(HttpServerRequest request) {
-        if (matches(request)) {
-            return routes.stream()
-                .flatMap(JMAPRoutes::routes);
-        }
-        return Stream.of();
-    }
+    @VisibleForTesting
+    Version parse(String version) {
+        Preconditions.checkNotNull(version);
 
-    private boolean matches(HttpServerRequest request) {
-        return version.equals(extractRequestVersionHeader(request));
+        return supportedVersions.stream()
+            .filter(jmapVersion -> jmapVersion.asString().equalsIgnoreCase(version))
+            .findFirst()
+            .orElseThrow(() -> new IllegalArgumentException(version + " is not a supported version"));
     }
 
-    private Version extractRequestVersionHeader(HttpServerRequest request) {
+    Version parseRequestVersionHeader(HttpServerRequest request) {
         return asVersion(request)
             .filter(nameValuePair -> nameValuePair.getName().equals(JMAP_VERSION_HEADER))
             .map(NameValuePair::getValue)
-            .map(Version::of)
+            .map(this::parse)
             .findFirst()
             .orElse(Version.DRAFT);
     }
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index 759bbb3..992e622 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -81,10 +81,15 @@ class JMAPServerTest {
             new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.RFC8621))
     );
 
+    private static final ImmutableSet<Version> SUPPORTED_VERSIONS = ImmutableSet.of(
+        Version.DRAFT,
+        Version.RFC8621
+    );
 
     @Test
     void serverShouldAnswerWhenStarted() {
-        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS);
+        VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS, versionParser);
         jmapServer.start();
 
         try {
@@ -102,14 +107,16 @@ class JMAPServerTest {
 
     @Test
     void startShouldNotThrowWhenConfigurationDisabled() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
+        VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS, versionParser);
 
         assertThatCode(jmapServer::start).doesNotThrowAnyException();
     }
 
     @Test
     void stopShouldNotThrowWhenConfigurationDisabled() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
+        VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS, versionParser);
         jmapServer.start();
 
         assertThatCode(jmapServer::stop).doesNotThrowAnyException();
@@ -117,7 +124,8 @@ class JMAPServerTest {
 
     @Test
     void getPortShouldThrowWhenServerIsNotStarted() {
-        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS);
+        VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+        JMAPServer jmapServer = new JMAPServer(TEST_CONFIGURATION, NO_ROUTES_HANDLERS, versionParser);
 
         assertThatThrownBy(jmapServer::getPort)
             .isInstanceOf(IllegalStateException.class);
@@ -125,7 +133,8 @@ class JMAPServerTest {
 
     @Test
     void getPortShouldThrowWhenDisabledConfiguration() {
-        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS);
+        VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+        JMAPServer jmapServer = new JMAPServer(DISABLED_CONFIGURATION, NO_ROUTES_HANDLERS, versionParser);
         jmapServer.start();
 
         assertThatThrownBy(jmapServer::getPort)
@@ -138,7 +147,8 @@ class JMAPServerTest {
 
         @BeforeEach
         void setUp() {
-            server = new JMAPServer(TEST_CONFIGURATION, FAKE_ROUTES_HANDLERS);
+            VersionParser versionParser = new VersionParser(SUPPORTED_VERSIONS);
+            server = new JMAPServer(TEST_CONFIGURATION, FAKE_ROUTES_HANDLERS, versionParser);
             server.start();
 
             RestAssured.requestSpecification = new RequestSpecBuilder()
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionParserTest.java
similarity index 67%
copy from server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
copy to server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionParserTest.java
index c94a8f7..17e1882 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionParserTest.java
@@ -22,43 +22,50 @@ package org.apache.james.jmap;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import nl.jqno.equalsverifier.EqualsVerifier;
+import com.google.common.collect.ImmutableSet;
 
-class VersionTest {
-    @Test
-    void shouldRespectBeanContract() {
-        EqualsVerifier.forClass(Version.class)
-            .verify();
+class VersionParserTest {
+    private static final ImmutableSet<Version> SUPPORTED_VERSIONS = ImmutableSet.of(
+        Version.DRAFT,
+        Version.RFC8621
+    );
+
+    private VersionParser versionParser;
+
+    @BeforeEach
+    void setUp() {
+        versionParser = new VersionParser(SUPPORTED_VERSIONS);
     }
 
     @Test
-    void ofShouldReturnCorrectValue() {
+    void parseShouldReturnCorrectValue() {
         String version = "rfc-8621";
 
-        assertThat(Version.of(version)).isEqualTo(Version.RFC8621);
+        assertThat(versionParser.parse(version)).isEqualTo(Version.RFC8621);
     }
 
     @Test
-    void ofShouldThrowWhenVersionNotKnown() {
+    void parseShouldThrowWhenVersionNotKnown() {
         String version = "unknown";
 
-        assertThatThrownBy(() -> Version.of(version))
+        assertThatThrownBy(() -> versionParser.parse(version))
             .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
-    void ofShouldThrowWhenVersionIsNull() {
-        assertThatThrownBy(() -> Version.of(null))
+    void parseShouldThrowWhenVersionIsNull() {
+        assertThatThrownBy(() -> versionParser.parse(null))
             .isInstanceOf(NullPointerException.class);
     }
 
     @Test
-    void ofShouldThrowWhenVersionIsEmpty() {
+    void parseShouldThrowWhenVersionIsEmpty() {
         String version = "";
 
-        assertThatThrownBy(() -> Version.of(version))
+        assertThatThrownBy(() -> versionParser.parse(version))
             .isInstanceOf(IllegalArgumentException.class);
     }
 }
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
index c94a8f7..704bec3 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/VersionTest.java
@@ -19,9 +19,6 @@
 
 package org.apache.james.jmap;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
 import org.junit.jupiter.api.Test;
 
 import nl.jqno.equalsverifier.EqualsVerifier;
@@ -32,33 +29,4 @@ class VersionTest {
         EqualsVerifier.forClass(Version.class)
             .verify();
     }
-
-    @Test
-    void ofShouldReturnCorrectValue() {
-        String version = "rfc-8621";
-
-        assertThat(Version.of(version)).isEqualTo(Version.RFC8621);
-    }
-
-    @Test
-    void ofShouldThrowWhenVersionNotKnown() {
-        String version = "unknown";
-
-        assertThatThrownBy(() -> Version.of(version))
-            .isInstanceOf(IllegalArgumentException.class);
-    }
-
-    @Test
-    void ofShouldThrowWhenVersionIsNull() {
-        assertThatThrownBy(() -> Version.of(null))
-            .isInstanceOf(NullPointerException.class);
-    }
-
-    @Test
-    void ofShouldThrowWhenVersionIsEmpty() {
-        String version = "";
-
-        assertThatThrownBy(() -> Version.of(version))
-            .isInstanceOf(IllegalArgumentException.class);
-    }
 }


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


[james-project] 07/15: JAMES-3092 Move JMAPUrls to jmap module

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f59c6dfe3637d50b18a1613d673c2a8f8e24c319
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Mar 17 14:56:55 2020 +0700

    JAMES-3092 Move JMAPUrls to jmap module
---
 .../src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java | 3 ++-
 .../src/main/java/org/apache/james/jmap/http/DownloadRoutes.java       | 2 +-
 .../src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java        | 2 +-
 .../src/main/java/org/apache/james/jmap/http/UploadRoutes.java         | 2 +-
 .../src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java    | 2 +-
 .../http => jmap/src/main/java/org/apache/james/jmap}/JMAPUrls.java    | 2 +-
 6 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index db645dd..4c33bc9 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -28,7 +28,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.OK;
 import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
 import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE;
 import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8;
-import static org.apache.james.jmap.http.JMAPUrls.AUTHENTICATION;
+import static org.apache.james.jmap.JMAPUrls.AUTHENTICATION;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAction;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAuthContext;
 import static org.apache.james.jmap.http.LoggingHelper.jmapContext;
@@ -42,6 +42,7 @@ import javax.inject.Inject;
 
 import org.apache.james.core.Username;
 import org.apache.james.jmap.JMAPRoutes;
+import org.apache.james.jmap.JMAPUrls;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.draft.api.AccessTokenManager;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index 57b99bd..c32edc6 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -22,7 +22,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
 import static org.apache.james.jmap.HttpConstants.TEXT_PLAIN_CONTENT_TYPE;
-import static org.apache.james.jmap.http.JMAPUrls.DOWNLOAD;
+import static org.apache.james.jmap.JMAPUrls.DOWNLOAD;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAction;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAuthContext;
 import static org.apache.james.jmap.http.LoggingHelper.jmapContext;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index 9a483e2..f8dc7ca 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -21,7 +21,7 @@ package org.apache.james.jmap.http;
 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
 import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE;
-import static org.apache.james.jmap.http.JMAPUrls.JMAP;
+import static org.apache.james.jmap.JMAPUrls.JMAP;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAuthContext;
 import static org.apache.james.jmap.http.LoggingHelper.jmapContext;
 import static org.apache.james.util.ReactorUtils.logOnError;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index ff0f306..2465b0b 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -22,7 +22,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static io.netty.handler.codec.http.HttpResponseStatus.CREATED;
 import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8;
-import static org.apache.james.jmap.http.JMAPUrls.UPLOAD;
+import static org.apache.james.jmap.JMAPUrls.UPLOAD;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAction;
 import static org.apache.james.jmap.http.LoggingHelper.jmapAuthContext;
 import static org.apache.james.jmap.http.LoggingHelper.jmapContext;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
index 5cb214f..7973c98 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
@@ -21,7 +21,7 @@ package org.apache.james.jmap.http;
 import static io.restassured.RestAssured.given;
 import static io.restassured.config.EncoderConfig.encoderConfig;
 import static io.restassured.config.RestAssuredConfig.newConfig;
-import static org.apache.james.jmap.http.JMAPUrls.JMAP;
+import static org.apache.james.jmap.JMAPUrls.JMAP;
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPUrls.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPUrls.java
similarity index 97%
rename from server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPUrls.java
rename to server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPUrls.java
index 9252165..f7256c7 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPUrls.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPUrls.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.http;
+package org.apache.james.jmap;
 
 public interface JMAPUrls {
     String JMAP = "/jmap";


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


[james-project] 10/15: JAMES-3092 Simplify the Y versioning routing in jmap

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6d658f142300a03bcfc5ed60e2af883bad3d4052
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Mar 24 14:03:02 2020 +0700

    JAMES-3092 Simplify the Y versioning routing in jmap
---
 .../james/jmap/http/AuthenticationRoutes.java      |  10 +-
 .../org/apache/james/jmap/http/DownloadRoutes.java |  14 +--
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  |   6 +-
 .../org/apache/james/jmap/http/UploadRoutes.java   |   6 +-
 .../apache/james/jmap/http/JMAPApiRoutesTest.java  |   6 +-
 .../main/java/org/apache/james/jmap/Endpoint.java  |  24 ++--
 .../main/java/org/apache/james/jmap/JMAPRoute.java |  30 ++++-
 .../java/org/apache/james/jmap/JMAPRoutes.java     |   2 +-
 .../java/org/apache/james/jmap/JMAPServer.java     |  72 ++---------
 .../org/apache/james/jmap/UriPathTemplate.java     | 139 +++++++++++++++++++++
 .../src/main/java/org/apache/james/jmap/Verb.java  |  44 -------
 .../java/org/apache/james/jmap/JMAPServerTest.java |   9 +-
 12 files changed, 215 insertions(+), 147 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index ed06cbf..9412861 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -46,7 +46,6 @@ import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.JMAPUrls;
-import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.Version;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.draft.api.AccessTokenManager;
@@ -70,6 +69,7 @@ import org.slf4j.LoggerFactory;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import io.netty.handler.codec.http.HttpMethod;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
@@ -108,10 +108,10 @@ public class AuthenticationRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(Verb.POST, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(Verb.GET, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
-            new JMAPRoute(new Endpoint(Verb.DELETE, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::delete)),
-            new JMAPRoute(new Endpoint(Verb.OPTIONS, AUTHENTICATION), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::returnEndPointsResponse)),
+            new JMAPRoute(new Endpoint(HttpMethod.DELETE, AUTHENTICATION), Version.DRAFT, JMAPRoutes.corsHeaders(this::delete)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION), Version.DRAFT, CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index 0f97b15..c8b35a2 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -40,7 +40,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
@@ -66,6 +65,7 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
 
 import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.HttpMethod;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
@@ -101,12 +101,12 @@ public class DownloadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(Verb.POST, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromId)),
-            new JMAPRoute(new Endpoint(Verb.GET, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromId)),
-            new JMAPRoute(new Endpoint(Verb.POST, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromIdAndName)),
-            new JMAPRoute(new Endpoint(Verb.GET, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromIdAndName)),
-            new JMAPRoute(new Endpoint(Verb.OPTIONS, DOWNLOAD_FROM_ID), Version.DRAFT, CORS_CONTROL),
-            new JMAPRoute(new Endpoint(Verb.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromId)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromId)),
+            new JMAPRoute(new Endpoint(HttpMethod.POST, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::postFromIdAndName)),
+            new JMAPRoute(new Endpoint(HttpMethod.GET, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, JMAPRoutes.corsHeaders(this::getFromIdAndName)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID), Version.DRAFT, CORS_CONTROL),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, DOWNLOAD_FROM_ID_AND_NAME), Version.DRAFT, CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index 82b8669..f574bff 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -34,7 +34,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
@@ -53,6 +52,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import io.netty.handler.codec.http.HttpMethod;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
@@ -88,8 +88,8 @@ public class JMAPApiRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(Verb.POST, JMAP), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(Verb.OPTIONS, JMAP), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, JMAP), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, JMAP), Version.DRAFT, CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index 253a076..92c289d 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -38,7 +38,6 @@ import javax.inject.Inject;
 import org.apache.james.jmap.Endpoint;
 import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
-import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.Version;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
@@ -57,6 +56,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import com.google.common.io.ByteStreams;
 
+import io.netty.handler.codec.http.HttpMethod;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 import reactor.netty.http.server.HttpServerRequest;
@@ -90,8 +90,8 @@ public class UploadRoutes implements JMAPRoutes {
     @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
-            new JMAPRoute(new Endpoint(Verb.POST, UPLOAD), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
-            new JMAPRoute(new Endpoint(Verb.OPTIONS, UPLOAD), Version.DRAFT, CORS_CONTROL)
+            new JMAPRoute(new Endpoint(HttpMethod.POST, UPLOAD), Version.DRAFT, JMAPRoutes.corsHeaders(this::post)),
+            new JMAPRoute(new Endpoint(HttpMethod.OPTIONS, UPLOAD), Version.DRAFT, CORS_CONTROL)
         );
     }
 
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
index f688099..43f8fa5 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JMAPApiRoutesTest.java
@@ -32,7 +32,6 @@ import java.nio.charset.StandardCharsets;
 
 import org.apache.james.core.Username;
 import org.apache.james.jmap.JMAPRoute;
-import org.apache.james.jmap.Verb;
 import org.apache.james.jmap.draft.methods.ErrorResponse;
 import org.apache.james.jmap.draft.methods.Method;
 import org.apache.james.jmap.draft.methods.RequestHandler;
@@ -48,6 +47,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
+import io.netty.handler.codec.http.HttpMethod;
 import io.restassured.RestAssured;
 import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.http.ContentType;
@@ -76,13 +76,13 @@ public class JMAPApiRoutesTest {
             mockedAuthFilter, mockedUserProvisionner, mockedMailboxesProvisionner);
 
         JMAPRoute postApiRoute = jmapApiRoutes.routes()
-            .filter(jmapRoute -> jmapRoute.getEndpoint().getVerb().equals(Verb.POST))
+            .filter(jmapRoute -> jmapRoute.getEndpoint().getMethod().equals(HttpMethod.POST))
             .findFirst()
             .get();
 
         server = HttpServer.create()
             .port(RANDOM_PORT)
-            .route(routes -> routes.post(postApiRoute.getEndpoint().getPath(), (req, res) -> postApiRoute.getAction().apply(req, res)))
+            .route(routes -> routes.post(postApiRoute.getEndpoint().getPath(), (req, res) -> postApiRoute.getAction().handleRequest(req, res)))
             .bindNow();
 
         RestAssured.requestSpecification = new RequestSpecBuilder()
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
index 3c878df..222d40b 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Endpoint.java
@@ -21,27 +21,31 @@ package org.apache.james.jmap;
 
 import java.util.Objects;
 
-import reactor.netty.http.server.HttpServerRoutes;
+import io.netty.handler.codec.http.HttpMethod;
+import reactor.netty.http.server.HttpServerRequest;
 
 public class Endpoint {
-    private final Verb verb;
+    private final HttpMethod method;
     private final String path;
+    private final UriPathTemplate uriPathTemplate;
 
-    public Endpoint(Verb verb, String path) {
-        this.verb = verb;
+    public Endpoint(HttpMethod method, String path) {
+        this.method = method;
         this.path = path;
+        this.uriPathTemplate = new UriPathTemplate(path);
     }
 
-    public Verb getVerb() {
-        return verb;
+    public HttpMethod getMethod() {
+        return method;
     }
 
     public String getPath() {
         return path;
     }
 
-    HttpServerRoutes registerRoute(HttpServerRoutes builder, JMAPRoute.Action action) {
-        return verb.registerRoute(builder, this.path, action);
+    public boolean matches(HttpServerRequest request) {
+        return method.equals(request.method())
+            && uriPathTemplate.matches(request.uri());
     }
 
     @Override
@@ -49,7 +53,7 @@ public class Endpoint {
         if (o instanceof Endpoint) {
             Endpoint endpoint = (Endpoint) o;
 
-            return Objects.equals(this.verb, endpoint.verb)
+            return Objects.equals(this.method, endpoint.method)
                 && Objects.equals(this.path, endpoint.path);
         }
         return false;
@@ -57,6 +61,6 @@ public class Endpoint {
 
     @Override
     public final int hashCode() {
-        return Objects.hash(verb, path);
+        return Objects.hash(method, path);
     }
 }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
index d4d8986..aaa3909 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoute.java
@@ -19,7 +19,11 @@
 
 package org.apache.james.jmap;
 
-import java.util.function.BiFunction;
+import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.Stream;
 
 import org.reactivestreams.Publisher;
 
@@ -27,10 +31,12 @@ import reactor.netty.http.server.HttpServerRequest;
 import reactor.netty.http.server.HttpServerResponse;
 
 public class JMAPRoute {
-    public interface Action extends BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> {
-
+    public interface Action {
+        Publisher<Void> handleRequest(HttpServerRequest request, HttpServerResponse response);
     }
 
+    private static final String JMAP_VERSION_HEADER = "jmapVersion=";
+
     private final Endpoint endpoint;
     private final Version version;
     private final Action action;
@@ -52,4 +58,22 @@ public class JMAPRoute {
     public Action getAction() {
         return action;
     }
+
+    public boolean matches(HttpServerRequest request) {
+        return getVersion().equals(extractRequestVersionHeader(request))
+            && getEndpoint().matches(request);
+    }
+
+    private Version extractRequestVersionHeader(HttpServerRequest request) {
+        return Optional.ofNullable(request.requestHeaders().get(ACCEPT))
+                .map(s -> s.split(";"))
+                .map(Arrays::stream)
+                .orElse(Stream.of())
+            .map(value -> value.trim().toLowerCase())
+            .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
+            .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
+            .map(Version::of)
+            .findFirst()
+            .orElse(Version.DRAFT);
+    }
 }
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
index 779c8ca..f2a8b09 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
@@ -36,7 +36,7 @@ public interface JMAPRoutes {
     JMAPRoute.Action CORS_CONTROL = corsHeaders((req, res) -> res.send());
 
     static JMAPRoute.Action corsHeaders(JMAPRoute.Action action) {
-        return (req, res) -> action.apply(req, res
+        return (req, res) -> action.handleRequest(req, res
             .header("Access-Control-Allow-Origin", "*")
             .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
             .header("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept"));
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index 12426ed..6b354fe 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -19,13 +19,9 @@
 
 package org.apache.james.jmap;
 
-import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
 import java.util.Optional;
 import java.util.Set;
 
@@ -36,18 +32,12 @@ import org.apache.james.lifecycle.api.Startable;
 import org.apache.james.util.Port;
 import org.slf4j.LoggerFactory;
 
-import com.github.steveash.guavate.Guavate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-
 import reactor.netty.DisposableServer;
 import reactor.netty.http.server.HttpServer;
 import reactor.netty.http.server.HttpServerRequest;
-import reactor.netty.http.server.HttpServerRoutes;
 
 public class JMAPServer implements Startable {
     private static final int RANDOM_PORT = 0;
-    private static final String JMAP_VERSION_HEADER = "jmapVersion=";
 
     private final JMAPConfiguration configuration;
     private final Set<JMAPRoutes> jmapRoutes;
@@ -67,17 +57,12 @@ public class JMAPServer implements Startable {
     }
 
     public void start() {
-        ImmutableListMultimap<Endpoint, JMAPRoute> collect = jmapRoutes.stream()
-            .flatMap(JMAPRoutes::routes)
-            .collect(Guavate.toImmutableListMultimap(JMAPRoute::getEndpoint));
-
         if (configuration.isEnabled()) {
             server = Optional.of(HttpServer.create()
                 .port(configuration.getPort()
                     .map(Port::getValue)
                     .orElse(RANDOM_PORT))
-                .route(routes -> jmapRoutes.forEach(jmapRoute -> collect.asMap().forEach(
-                    (endpoint, route) -> injectRoutes(routes, endpoint, route))))
+                .handle((request, response) -> handleVersionRoute(request).handleRequest(request, response))
                 .wiretap(wireTapEnabled())
                 .bindNow());
         }
@@ -87,61 +72,20 @@ public class JMAPServer implements Startable {
         return LoggerFactory.getLogger("org.apache.james.jmap.wire").isTraceEnabled();
     }
 
-    private HttpServerRoutes injectRoutes(HttpServerRoutes builder, Endpoint endpoint, Collection<JMAPRoute> routesList) {
-        if (routesList.size() == 1) {
-            JMAPRoute next = routesList.iterator().next();
-
-            return endpoint.registerRoute(builder, (req, res) ->
-                getExistingRoute(extractRequestVersionHeader(req), next).apply(req, res));
-        } else if (routesList.size() == 2) {
-            ImmutableList<JMAPRoute> sorted = routesList.stream()
-                .sorted(Comparator.comparing(JMAPRoute::getVersion))
-                .collect(Guavate.toImmutableList());
-            JMAPRoute draftRoute = sorted.get(0);
-            JMAPRoute rfc8621Route = sorted.get(1);
-
-            return endpoint.registerRoute(builder, (req, res) ->
-                chooseVersionRoute(extractRequestVersionHeader(req), draftRoute, rfc8621Route).apply(req, res));
-        }
-        return builder;
-    }
-
-    private JMAPRoute.Action getExistingRoute(String version, JMAPRoute route) {
+    private JMAPRoute.Action handleVersionRoute(HttpServerRequest request) {
         try {
-            if (Version.of(version).equals(route.getVersion())) {
-                return route.getAction();
-            }
+            return jmapRoutes.stream()
+                .flatMap(JMAPRoutes::routes)
+                .filter(jmapRoute -> jmapRoute.matches(request))
+                .map(JMAPRoute::getAction)
+                .findFirst()
+                .orElse((req, res) -> res.status(NOT_FOUND).send());
         } catch (IllegalArgumentException e) {
             return (req, res) -> res.status(BAD_REQUEST).send();
         }
-        return (req, res) -> res.status(NOT_FOUND).send();
     }
 
-    private JMAPRoute.Action chooseVersionRoute(String version, JMAPRoute draftRoute, JMAPRoute rfc8621Route) {
-        try {
-            if (hasRfc8621AcceptHeader(version)) {
-                return rfc8621Route.getAction();
-            }
-        } catch (IllegalArgumentException e) {
-            return (req, res) -> res.status(BAD_REQUEST).send();
-        }
-        return draftRoute.getAction();
-    }
 
-    private boolean hasRfc8621AcceptHeader(String version) {
-        return Version.of(version).equals(Version.RFC8621);
-    }
-
-    private String extractRequestVersionHeader(HttpServerRequest request) {
-        return Arrays.stream(request.requestHeaders()
-                .get(ACCEPT)
-                .split(";"))
-            .map(value -> value.trim().toLowerCase())
-            .filter(value -> value.startsWith(JMAP_VERSION_HEADER.toLowerCase()))
-            .map(value -> value.substring(JMAP_VERSION_HEADER.length()))
-            .findFirst()
-            .orElse(Version.DRAFT.getVersion());
-    }
 
     @PreDestroy
     public void stop() {
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
new file mode 100644
index 0000000..a9b3759
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/UriPathTemplate.java
@@ -0,0 +1,139 @@
+/****************************************************************
+ * 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is copied from io.projectreactor.netty:reactor-netty version0.9.0-RELEASE and was originaly license under
+ * Apache license version 2. Copied because of private access.
+ * <p>
+ * Represents a URI template. A URI template is a URI-like String that contains
+ * variables enclosed by braces (<code>{</code>, <code>}</code>), which can be
+ * expanded to produce an actual URI.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @author Jon Brisbin
+ * @see <a href="https://tools.ietf.org/html/rfc6570">RFC 6570: URI Templates</a>
+ */
+public class UriPathTemplate {
+    private static final Pattern FULL_SPLAT_PATTERN =
+        Pattern.compile("[\\*][\\*]");
+    private static final String FULL_SPLAT_REPLACEMENT = ".*";
+
+    private static final Pattern NAME_SPLAT_PATTERN =
+        Pattern.compile("\\{([^/]+?)\\}[\\*][\\*]");
+
+    private static final Pattern NAME_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
+    // JDK 6 doesn't support named capture groups
+
+    private final List<String> pathVariables =
+        new ArrayList<>();
+    private final HashMap<String, Matcher> matchers =
+        new HashMap<>();
+    private final HashMap<String, Map<String, String>> vars =
+        new HashMap<>();
+
+    private final Pattern uriPattern;
+
+    private static String getNameSplatReplacement(String name) {
+        return "(?<" + name + ">.*)";
+    }
+
+    private static String getNameReplacement(String name) {
+        return "(?<" + name + ">[^\\/]*)";
+    }
+
+    static String filterQueryParams(String uri) {
+        int hasQuery = uri.lastIndexOf('?');
+        if (hasQuery != -1) {
+            return uri.substring(0, hasQuery);
+        } else {
+            return uri;
+        }
+    }
+
+    /**
+     * Creates a new {@code UriPathTemplate} from the given {@code uriPattern}.
+     *
+     * @param uriPattern The pattern to be used by the template
+     */
+    UriPathTemplate(String uriPattern) {
+        String s = "^" + filterQueryParams(uriPattern);
+
+        Matcher m = NAME_SPLAT_PATTERN.matcher(s);
+        while (m.find()) {
+            for (int i = 1; i <= m.groupCount(); i++) {
+                String name = m.group(i);
+                pathVariables.add(name);
+                s = m.replaceFirst(getNameSplatReplacement(name));
+                m.reset(s);
+            }
+        }
+
+        m = NAME_PATTERN.matcher(s);
+        while (m.find()) {
+            for (int i = 1; i <= m.groupCount(); i++) {
+                String name = m.group(i);
+                pathVariables.add(name);
+                s = m.replaceFirst(getNameReplacement(name));
+                m.reset(s);
+            }
+        }
+
+        m = FULL_SPLAT_PATTERN.matcher(s);
+        while (m.find()) {
+            s = m.replaceAll(FULL_SPLAT_REPLACEMENT);
+            m.reset(s);
+        }
+
+        this.uriPattern = Pattern.compile(s + "$");
+    }
+
+    /**
+     * Tests the given {@code uri} against this template, returning {@code true} if
+     * the uri matches the template, {@code false} otherwise.
+     *
+     * @param uri The uri to match
+     * @return {@code true} if there's a match, {@code false} otherwise
+     */
+    public boolean matches(String uri) {
+        return matcher(uri).matches();
+    }
+
+    private Matcher matcher(String uri) {
+        uri = filterQueryParams(uri);
+        Matcher m = matchers.get(uri);
+        if (null == m) {
+            m = uriPattern.matcher(uri);
+            synchronized (matchers) {
+                matchers.put(uri, m);
+            }
+        }
+        return m;
+    }
+
+}
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
deleted file mode 100644
index 7a94547..0000000
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/Verb.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/****************************************************************
- * 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;
-
-import reactor.netty.http.server.HttpServerRoutes;
-
-public enum Verb {
-    GET,
-    POST,
-    DELETE,
-    OPTIONS;
-
-    HttpServerRoutes registerRoute(HttpServerRoutes builder, String path, JMAPRoute.Action action) {
-        switch (this) {
-            case GET:
-                return builder.get(path, action);
-            case POST:
-                return builder.post(path, action);
-            case DELETE:
-                return builder.delete(path, action);
-            case OPTIONS:
-                return builder.options(path, action);
-            default:
-                return builder;
-        }
-    }
-}
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index b03a836..a0eec47 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -42,6 +42,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
+import io.netty.handler.codec.http.HttpMethod;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.restassured.RestAssured;
 import io.restassured.builder.RequestSpecBuilder;
@@ -62,12 +63,12 @@ class JMAPServerTest {
     private static final ImmutableSet<JMAPRoutes> NO_ROUTES = ImmutableSet.of();
 
     private static final ImmutableSet<Endpoint> AUTHENTICATION_ENDPOINTS = ImmutableSet.of(
-        new Endpoint(Verb.POST, JMAPUrls.AUTHENTICATION),
-        new Endpoint(Verb.GET, JMAPUrls.AUTHENTICATION)
+        new Endpoint(HttpMethod.POST, JMAPUrls.AUTHENTICATION),
+        new Endpoint(HttpMethod.GET, JMAPUrls.AUTHENTICATION)
     );
     private static final ImmutableSet<Endpoint> JMAP_ENDPOINTS = ImmutableSet.of(
-        new Endpoint(Verb.POST, JMAPUrls.JMAP),
-        new Endpoint(Verb.DELETE, JMAPUrls.JMAP)
+        new Endpoint(HttpMethod.POST, JMAPUrls.JMAP),
+        new Endpoint(HttpMethod.DELETE, JMAPUrls.JMAP)
     );
     private static final ImmutableSet<JMAPRoutes> FAKE_ROUTES = ImmutableSet.of(
         new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.DRAFT),


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


[james-project] 05/15: JAMES-3136 Limit message projection inconsistency with a retry strategy

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c8e97f6306d63f07cb9afd396abc8520b5f7b61a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 11:38:15 2020 +0700

    JAMES-3136 Limit message projection inconsistency with a retry strategy
---
 .../mailbox/cassandra/mail/CassandraMessageIdMapper.java  |  8 +++++++-
 .../cassandra/mail/CassandraMessageIdMapperTest.java      | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index 4d88c9a..3cfd722 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.time.Duration;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
@@ -58,6 +59,10 @@ import reactor.core.scheduler.Schedulers;
 public class CassandraMessageIdMapper implements MessageIdMapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMessageIdMapper.class);
 
+    private static final int MAX_RETRY = 5;
+    private static final Duration MIN_RETRY_BACKOFF = Duration.ofMillis(10);
+    private static final Duration MAX_RETRY_BACKOFF = Duration.ofMillis(1000);
+
     private final MailboxMapper mailboxMapper;
     private final CassandraMailboxDAO mailboxDAO;
     private final CassandraMessageIdToImapUidDAO imapUidDAO;
@@ -128,7 +133,8 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         ComposedMessageIdWithMetaData composedMessageIdWithMetaData = createMetadataFor(mailboxMessage);
         messageDAO.save(mailboxMessage)
             .thenEmpty(imapUidDAO.insert(composedMessageIdWithMetaData))
-            .thenEmpty(messageIdDAO.insert(composedMessageIdWithMetaData))
+            .thenEmpty(messageIdDAO.insert(composedMessageIdWithMetaData)
+                .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF))
             .thenEmpty(indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
             .block();
     }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
index 582569a..891529a 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
@@ -204,5 +204,20 @@ class CassandraMessageIdMapperTest extends MessageIdMapperTest {
                     .hasSize(1);
             }));
         }
+
+        @Test
+        void addShouldRetryMessageDenormalization(CassandraCluster cassandra) throws Exception {
+            cassandra.getConf()
+                .registerScenario(fail()
+                    .times(5)
+                    .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags)"));
+
+            message1.setUid(mapperProvider.generateMessageUid());
+            message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+            sut.save(message1);
+
+            assertThat(sut.find(ImmutableList.of(message1.getMessageId()), MessageMapper.FetchType.Metadata))
+                .hasSize(1);
+        }
     }
 }


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