You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/09/18 06:09:42 UTC

[james-project] branch master updated (3205e7c -> f5be352)

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

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


    from 3205e7c  JAMES-3377 filter after receivedAt
     new 2e704d9  JAMES-3376 Document upcoming Mailbox/query limitations
     new cca3bb6  JAMES-3376 SystemMailboxesProvider can be reactive
     new 7149fa8  JAMES-3376 Implement Mailbox/query to retrieve mailbox by role
     new d3b74b6  JAMES-3376 BackReferences should allow reading arrays element
     new f5be352  JAMES-3376 Demonstrate INBOX content loading with back-references

The 5 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:
 .../james/mailbox/SystemMailboxesProvider.java     |  11 +-
 .../mailbox/store/SystemMailboxesProviderImpl.java |  21 +-
 .../store/SystemMailboxesProviderImplTest.java     |   5 +-
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   4 +-
 .../james/jmap/draft/methods/SendMDNProcessor.java |   5 +-
 .../methods/SetMessagesCreationProcessor.java      |   6 +-
 .../draft/methods/SetMessagesUpdateProcessor.java  |   7 +-
 .../methods/SetMessagesCreationProcessorTest.java  |  11 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |  11 +-
 ...java => DistributedMailboxQueryMethodTest.java} |   4 +-
 .../rfc8621/contract/BackReferenceContract.scala   | 126 ++++++
 .../contract/MailboxQueryMethodContract.scala      | 449 +++++++++++++++++++++
 ...Test.java => MemoryMailboxQueryMethodTest.java} |   4 +-
 .../doc/specs/spec/mail/mailbox.mdown              |   9 +
 .../james/jmap/json/EmailQuerySerializer.scala     |   3 +-
 ...rializer.scala => MailboxQuerySerializer.scala} |  32 +-
 .../org/apache/james/jmap/mail/EmailQuery.scala    |  22 +-
 .../org/apache/james/jmap/mail/MailboxQuery.scala  |  26 +-
 .../james/jmap/method/EmailQueryMethod.scala       |   4 +-
 ...lQueryMethod.scala => MailboxQueryMethod.scala} |  61 ++-
 .../{mail/EmailQuery.scala => model/Query.scala}   |  29 +-
 .../james/jmap/routes/ProcessingContext.scala      |  66 ++-
 .../apache/james/jmap/json/BackReferenceTest.scala |  63 ++-
 23 files changed, 830 insertions(+), 149 deletions(-)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedEmailQueryMethodTest.java => DistributedMailboxQueryMethodTest.java} (94%)
 create mode 100644 server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
 copy server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/{MemoryBackReferenceTest.java => MemoryMailboxQueryMethodTest.java} (92%)
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/{EmailQuerySerializer.scala => MailboxQuerySerializer.scala} (60%)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxTable.java => server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala (66%)
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/{EmailQueryMethod.scala => MailboxQueryMethod.scala} (54%)
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/{mail/EmailQuery.scala => model/Query.scala} (65%)


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


[james-project] 04/05: JAMES-3376 BackReferences should allow reading arrays element

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

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

commit d3b74b6135cac3d6be746794fea26387e5eba7fc
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Sep 17 11:19:17 2020 +0700

    JAMES-3376 BackReferences should allow reading arrays element
---
 .../james/jmap/routes/ProcessingContext.scala      | 66 ++++++++++++++++++++--
 .../apache/james/jmap/json/BackReferenceTest.scala | 63 ++++++++++++++++++++-
 2 files changed, 122 insertions(+), 7 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
index 8402d48..8506f2f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
@@ -19,6 +19,9 @@
 
 package org.apache.james.jmap.routes
 
+import eu.timepit.refined.numeric.NonNegative
+import eu.timepit.refined.refineV
+import eu.timepit.refined.types.numeric.NonNegInt
 import org.apache.james.jmap.json.BackReferenceDeserializer
 import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
 import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
@@ -28,9 +31,11 @@ import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId}
 import org.apache.james.mailbox.model.MailboxId
 import play.api.libs.json.{JsArray, JsError, JsObject, JsResult, JsSuccess, JsValue, Reads}
 
+import scala.util.Try
+
 sealed trait JsonPathPart
 
-case class WildcardPart() extends JsonPathPart
+case object WildcardPart extends JsonPathPart
 
 case class PlainPart(name: String) extends JsonPathPart {
   def read(jsValue: JsValue): JsResult[JsValue] = jsValue match {
@@ -39,13 +44,63 @@ case class PlainPart(name: String) extends JsonPathPart {
   }
 }
 
+object ArrayElementPart {
+  def parse(string: String): Option[ArrayElementPart] = {
+    if (string.startsWith("[") && string.endsWith("]")) {
+      val positionPart: String = string.substring(1, string.length - 1)
+      Try(positionPart.toInt)
+        .fold(_ => None, fromInt)
+    } else {
+      None
+    }
+  }
+
+  private def fromInt(position: Int): Option[ArrayElementPart] =
+    refineV[NonNegative](position)
+      .fold(_ => None,
+        ref => Some(ArrayElementPart(ref)))
+}
+
+case class ArrayElementPart(position: NonNegInt) extends JsonPathPart {
+  def read(jsValue: JsValue): JsResult[JsValue] = jsValue match {
+    case JsArray(values) => values.lift(position.value)
+      .map(JsSuccess(_))
+      .getOrElse(JsError(s"Supplied array have no $position element"))
+    case _ => JsError("Expecting a JsArray but got a different structure")
+  }
+}
+
 object JsonPath {
   def parse(string: String): JsonPath = JsonPath(string.split('/').toList
     .flatMap {
-      case "" => None
-      case "*" => Some(WildcardPart())
-      case part => Some(PlainPart(part))
+      case "" => Nil
+      case "*" => List(WildcardPart)
+      case string if ArrayElementPart.parse(string).isDefined => ArrayElementPart.parse(string)
+      case part: String =>
+        val arrayElementPartPosition = part.indexOf('[')
+        if (arrayElementPartPosition < 0) {
+          asPlainPart(part)
+        } else if (arrayElementPartPosition == 0) {
+          asArrayElementPart(string)
+        } else {
+          asArrayElementInAnObject(string, part, arrayElementPartPosition)
+        }
     })
+
+  private def asPlainPart(part: String): List[JsonPathPart] = {
+    List(PlainPart(part))
+  }
+
+  private def asArrayElementInAnObject(string: String, part: String, arrayElementPartPosition: Int): List[JsonPathPart] = {
+    ArrayElementPart.parse(string.substring(arrayElementPartPosition))
+      .map(List(PlainPart(part.substring(0, arrayElementPartPosition)), _))
+      .getOrElse(List(PlainPart(part)))
+  }
+
+  private def asArrayElementPart(string: String): List[JsonPathPart] = {
+    List(ArrayElementPart.parse(string)
+      .getOrElse(PlainPart(string)))
+  }
 }
 
 case class JsonPath(parts: List[JsonPathPart]) {
@@ -55,7 +110,8 @@ case class JsonPath(parts: List[JsonPathPart]) {
       val tailAsJsonPath = JsonPath(tail)
       head match {
         case part: PlainPart => part.read(jsValue).flatMap(subPart => tailAsJsonPath.evaluate(subPart))
-        case _: WildcardPart => tailAsJsonPath.readWildcard(jsValue)
+        case part: ArrayElementPart => part.read(jsValue).flatMap(subPart => tailAsJsonPath.evaluate(subPart))
+        case WildcardPart => tailAsJsonPath.readWildcard(jsValue)
       }
   }
 
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
index 7906f7e..6d31491 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
@@ -21,10 +21,10 @@ package org.apache.james.jmap.json
 
 import eu.timepit.refined.auto._
 import org.apache.james.jmap.model.Invocation.{MethodCallId, MethodName}
-import org.apache.james.jmap.routes.{BackReference, JsonPath}
+import org.apache.james.jmap.routes.{ArrayElementPart, BackReference, JsonPath, PlainPart}
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
-import play.api.libs.json.{JsError, JsSuccess, Json}
+import play.api.libs.json.{JsError, JsString, JsSuccess, Json}
 
 
 class BackReferenceTest extends AnyWordSpec with Matchers {
@@ -45,6 +45,30 @@ class BackReferenceTest extends AnyWordSpec with Matchers {
     }
   }
 
+  "Array element parsing" should {
+    "succeed when poositive" in {
+      ArrayElementPart.parse("[1]") should equal(Some(ArrayElementPart(1)))
+    }
+    "succeed when zero" in {
+      ArrayElementPart.parse("[0]") should equal(Some(ArrayElementPart(0)))
+    }
+    "fail when negative" in {
+      ArrayElementPart.parse("[-1]") should equal(None)
+    }
+    "fail when not an int" in {
+      ArrayElementPart.parse("[invalid]") should equal(None)
+    }
+    "fail when not closed" in {
+      ArrayElementPart.parse("[0") should equal(None)
+    }
+    "fail when not open" in {
+      ArrayElementPart.parse("0]") should equal(None)
+    }
+    "fail when no bracket" in {
+      ArrayElementPart.parse("0") should equal(None)
+    }
+  }
+
   "JsonPath evaluation" should {
     "noop when empty" in {
       val jsonPath = JsonPath.parse("")
@@ -60,6 +84,41 @@ class BackReferenceTest extends AnyWordSpec with Matchers {
 
       jsonPath.evaluate(json) should equal(JsSuccess(expected))
     }
+    "succeed when array element is present and root" in {
+      val jsonPath = JsonPath.parse("[1]")
+      val json = Json.parse("""["1", "2", "3"]""")
+      val expected = JsString("2")
+
+      jsonPath.evaluate(json) should equal(JsSuccess(expected))
+    }
+    "succeed when first array element is present" in {
+      val jsonPath = JsonPath.parse("path[0]")
+      val json = Json.parse("""{"path" : ["1", "2", "3"]}""")
+      val expected = JsString("1")
+
+      jsonPath.evaluate(json) should equal(JsSuccess(expected))
+    }
+    "fail when overflow" in {
+      val jsonPath = JsonPath.parse("path[3]")
+      val json = Json.parse("""{"path" : ["1", "2", "3"]}""")
+
+      jsonPath.evaluate(json) shouldBe a[JsError]
+    }
+    "parse should default to plain part when negative" in {
+      JsonPath.parse("path[-1]") should equal(JsonPath(List(PlainPart("path[-1]"))))
+    }
+    "parse should default to plain part when not an int" in {
+      JsonPath.parse("path[invalid]") should equal(JsonPath(List(PlainPart("path[invalid]"))))
+    }
+    "parse should default to plain part when empty" in {
+      JsonPath.parse("path[]") should equal(JsonPath(List(PlainPart("path[]"))))
+    }
+    "parse should default to plain part when not closed" in {
+      JsonPath.parse("path[36") should equal(JsonPath(List(PlainPart("path[36"))))
+    }
+    "parse should default to plain part when not closed and root" in {
+      JsonPath.parse("[36") should equal(JsonPath(List(PlainPart("[36"))))
+    }
     "succeed when array part is present" in {
       val jsonPath = JsonPath.parse("path/*")
       val json = Json.parse("""{"path" : ["1", "2"]}""")


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


[james-project] 01/05: JAMES-3376 Document upcoming Mailbox/query limitations

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

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

commit 2e704d9b30c7b7a5b1fd709e500c59bc37da48ff
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Sep 16 16:58:06 2020 +0700

    JAMES-3376 Document upcoming Mailbox/query limitations
---
 server/protocols/jmap-rfc-8621/doc/specs/spec/mail/mailbox.mdown | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/mailbox.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/mailbox.mdown
index 954584f..ff12a9a 100644
--- a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/mailbox.mdown
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/mailbox.mdown
@@ -122,6 +122,15 @@ Since counts frequently change but other properties are generally only changed r
 
 ## Mailbox/query
 
+  <aside class="warning">
+    Partially implemented. `sortAsTree` and `filterAsTree` options are unsupported.
+
+    `FilterCondition` only accepts a `role` property. `filter` property needs to be a `FilterCondition`,
+    `FilterOperator` are not supported.
+
+    `position`, `anchor`, `anchorOffset`, `limit`, `sort`, `calculateTotal` properties are not supported for this `/query` call.
+  </aside>
+
 This is a standard "/query" method as described in [@!RFC8620], Section 5.5, but with the following additional request argument:
 
 - **sortAsTree**: `Boolean` (default: false)


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


[james-project] 02/05: JAMES-3376 SystemMailboxesProvider can be reactive

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

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

commit cca3bb6031f52b2de7e1b22ef10eef6d2dd2b4fd
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Sep 16 17:47:48 2020 +0700

    JAMES-3376 SystemMailboxesProvider can be reactive
---
 .../james/mailbox/SystemMailboxesProvider.java      | 11 +++++++----
 .../mailbox/store/SystemMailboxesProviderImpl.java  | 21 ++++++++++-----------
 .../store/SystemMailboxesProviderImplTest.java      |  5 +++--
 .../james/jmap/draft/methods/SendMDNProcessor.java  |  5 ++++-
 .../draft/methods/SetMessagesCreationProcessor.java |  6 +++++-
 .../draft/methods/SetMessagesUpdateProcessor.java   |  7 +++++--
 .../methods/SetMessagesCreationProcessorTest.java   | 11 ++++++-----
 .../methods/SetMessagesUpdateProcessorTest.java     | 11 ++++++-----
 8 files changed, 46 insertions(+), 31 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/SystemMailboxesProvider.java b/mailbox/api/src/main/java/org/apache/james/mailbox/SystemMailboxesProvider.java
index f88498c..58c0f57 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/SystemMailboxesProvider.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/SystemMailboxesProvider.java
@@ -19,17 +19,20 @@
 
 package org.apache.james.mailbox;
 
-import java.util.stream.Stream;
-
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxRoleNotFoundException;
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Flux;
 
 public interface SystemMailboxesProvider {
-    Stream<MessageManager> getMailboxByRole(Role aRole, Username username) throws MailboxException;
+    Publisher<MessageManager> getMailboxByRole(Role aRole, Username username) throws MailboxException;
 
     default MessageManager findMailbox(Role role, Username username) throws MailboxException {
-        return getMailboxByRole(role, username).findAny()
+        return Flux.from(getMailboxByRole(role, username))
+            .next()
+            .blockOptional()
             .orElseThrow(() -> new MailboxRoleNotFoundException(role));
     }
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/SystemMailboxesProviderImpl.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/SystemMailboxesProviderImpl.java
index 01eb2dc..e8a6eed 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/SystemMailboxesProviderImpl.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/SystemMailboxesProviderImpl.java
@@ -19,8 +19,6 @@
 
 package org.apache.james.mailbox.store;
 
-import java.util.stream.Stream;
-
 import javax.inject.Inject;
 
 import org.apache.james.core.Username;
@@ -29,7 +27,6 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.Role;
 import org.apache.james.mailbox.SystemMailboxesProvider;
-import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.MailboxMetaData;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -40,6 +37,9 @@ import com.github.fge.lambdas.Throwing;
 import com.github.fge.lambdas.functions.ThrowingFunction;
 import com.google.common.annotations.VisibleForTesting;
 
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
 public class SystemMailboxesProviderImpl implements SystemMailboxesProvider {
 
     private final MailboxManager mailboxManager;
@@ -51,14 +51,14 @@ public class SystemMailboxesProviderImpl implements SystemMailboxesProvider {
     }
 
     @Override
-    public Stream<MessageManager> getMailboxByRole(Role aRole, Username username) throws MailboxException {
+    public Flux<MessageManager> getMailboxByRole(Role aRole, Username username) {
         MailboxSession session = mailboxManager.createSystemSession(username);
         MailboxPath mailboxPath = MailboxPath.forUser(username, aRole.getDefaultMailbox());
-        try {
-            return Stream.of(mailboxManager.getMailbox(mailboxPath, session));
-        } catch (MailboxNotFoundException e) {
-            return searchMessageManagerByMailboxRole(aRole, username);
-        }
+
+        return Mono.fromCallable(() -> mailboxManager.getMailbox(mailboxPath, session))
+            .flux()
+            .onErrorResume(MailboxNotFoundException.class, e -> searchMessageManagerByMailboxRole(aRole, username));
+
     }
 
     private boolean hasRole(Role aRole, MailboxPath mailBoxPath) {
@@ -67,14 +67,13 @@ public class SystemMailboxesProviderImpl implements SystemMailboxesProvider {
             .orElse(false);
     }
 
-    private Stream<MessageManager> searchMessageManagerByMailboxRole(Role aRole, Username username) throws MailboxException {
+    private Flux<MessageManager> searchMessageManagerByMailboxRole(Role aRole, Username username) {
         MailboxSession session = mailboxManager.createSystemSession(username);
         ThrowingFunction<MailboxPath, MessageManager> loadMailbox = path -> mailboxManager.getMailbox(path, session);
         MailboxQuery mailboxQuery = MailboxQuery.privateMailboxesBuilder(session)
             .expression(new PrefixedWildcard(aRole.getDefaultMailbox()))
             .build();
         return mailboxManager.search(mailboxQuery, session)
-            .toStream()
             .map(MailboxMetaData::getPath)
             .filter(path -> hasRole(aRole, path))
             .map(Throwing.function(loadMailbox).sneakyThrow());
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/SystemMailboxesProviderImplTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/SystemMailboxesProviderImplTest.java
index fc0ca0d..5bdbf56 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/SystemMailboxesProviderImplTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/SystemMailboxesProviderImplTest.java
@@ -60,7 +60,8 @@ class SystemMailboxesProviderImplTest {
         when(mailboxManager.getMailbox(eq(MailboxFixture.INBOX_ALICE), eq(mailboxSession))).thenThrow(MailboxNotFoundException.class);
         when(mailboxManager.search(any(), any())).thenReturn(Flux.empty());
 
-        assertThat(systemMailboxProvider.getMailboxByRole(Role.INBOX, mailboxSession.getUser())).isEmpty();
+        assertThat(Flux.from(systemMailboxProvider.getMailboxByRole(Role.INBOX, mailboxSession.getUser())).toStream())
+            .isEmpty();
     }
 
     @Test
@@ -68,7 +69,7 @@ class SystemMailboxesProviderImplTest {
         when(mailboxManager.createSystemSession(MailboxFixture.ALICE)).thenReturn(mailboxSession);
         when(mailboxManager.getMailbox(eq(MailboxFixture.INBOX_ALICE), eq(mailboxSession))).thenReturn(inboxMessageManager);
 
-        assertThat(systemMailboxProvider.getMailboxByRole(Role.INBOX, mailboxSession.getUser()))
+        assertThat(Flux.from(systemMailboxProvider.getMailboxByRole(Role.INBOX, mailboxSession.getUser())).toStream())
             .hasSize(1)
             .containsOnly(inboxMessageManager);
     }
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java
index 0fb3bea..6af7e63 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java
@@ -55,6 +55,8 @@ import org.apache.james.server.core.Envelope;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import reactor.core.publisher.Flux;
+
 public class SendMDNProcessor implements SetMessagesProcessor {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(SendMDNProcessor.class);
@@ -165,7 +167,8 @@ public class SendMDNProcessor implements SetMessagesProcessor {
     }
 
     private MessageManager getOutbox(MailboxSession mailboxSession) throws MailboxException {
-        return systemMailboxesProvider.getMailboxByRole(Role.OUTBOX, mailboxSession.getUser())
+        return Flux.from(systemMailboxesProvider.getMailboxByRole(Role.OUTBOX, mailboxSession.getUser()))
+            .toStream()
             .findAny()
             .orElseThrow(() -> new IllegalStateException("User don't have an Outbox"));
     }
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java
index 9936468..ece4c20 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java
@@ -74,6 +74,8 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 
+import reactor.core.publisher.Flux;
+
 
 public class SetMessagesCreationProcessor implements SetMessagesProcessor {
 
@@ -335,7 +337,9 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
     }
 
     private Optional<MessageManager> getMailboxWithRole(MailboxSession mailboxSession, Role role) throws MailboxException {
-        return systemMailboxesProvider.getMailboxByRole(role, mailboxSession.getUser()).findFirst();
+        return Flux.from(systemMailboxesProvider.getMailboxByRole(role, mailboxSession.getUser()))
+            .toStream()
+            .findFirst();
     }
 
     private SetError buildSetErrorFromValidationResult(List<ValidationResult> validationErrors) {
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
index 23ddb1a..5716a54 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
@@ -74,6 +74,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
 import io.vavr.control.Try;
+import reactor.core.publisher.Flux;
 
 public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
 
@@ -268,7 +269,8 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
     }
 
     private List<MailboxId> mailboxIdFor(Role role, MailboxSession session) throws MailboxException {
-        return systemMailboxesProvider.getMailboxByRole(role, session.getUser())
+        return Flux.from(systemMailboxesProvider.getMailboxByRole(role, session.getUser()))
+            .toStream()
             .map(MessageManager::getId)
             .collect(Guavate.toImmutableList());
     }
@@ -293,7 +295,8 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
     }
 
     private Set<MailboxId> listMailboxIdsForRole(MailboxSession session, Role role) throws MailboxException {
-        return systemMailboxesProvider.getMailboxByRole(role, session.getUser())
+        return Flux.from(systemMailboxesProvider.getMailboxByRole(role, session.getUser()))
+            .toStream()
             .map(MessageManager::getId)
             .collect(Guavate.toImmutableSet());
     }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index fd3fd85..453f932 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.when;
 import java.net.UnknownHostException;
 import java.util.Optional;
 import java.util.function.Supplier;
-import java.util.stream.Stream;
 
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.core.Domain;
@@ -94,6 +93,8 @@ import org.junit.rules.ExpectedException;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import reactor.core.publisher.Flux;
+
 public class SetMessagesCreationProcessorTest {
     private static final Username USER = Username.of("user@example.com");
     private static final Username OTHER_USER = Username.of("other@example.com");
@@ -496,13 +497,13 @@ public class SetMessagesCreationProcessorTest {
         }
 
         @Override
-        public Stream<MessageManager> getMailboxByRole(Role aRole, Username username) {
+        public Flux<MessageManager> getMailboxByRole(Role aRole, Username username) {
             if (aRole.equals(Role.OUTBOX)) {
-                return outboxSupplier.get().stream();
+                return Flux.fromStream(outboxSupplier.get().stream());
             } else if (aRole.equals(Role.DRAFTS)) {
-                return draftsSupplier.get().stream();
+                return Flux.fromStream(draftsSupplier.get().stream());
             }
-            return Stream.empty();
+            return Flux.empty();
         }
     }
 }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index d6dd11c..aa01dcd 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -29,7 +29,6 @@ import static org.mockito.Mockito.when;
 import java.net.UnknownHostException;
 import java.util.Optional;
 import java.util.function.Supplier;
-import java.util.stream.Stream;
 
 import javax.mail.internet.AddressException;
 
@@ -82,6 +81,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import reactor.core.publisher.Flux;
+
 public class SetMessagesUpdateProcessorTest {
 
 
@@ -104,13 +105,13 @@ public class SetMessagesUpdateProcessorTest {
         }
 
         @Override
-        public Stream<MessageManager> getMailboxByRole(Role aRole, Username username) {
+        public Flux<MessageManager> getMailboxByRole(Role aRole, Username username) {
             if (aRole.equals(Role.OUTBOX)) {
-                return outboxSupplier.get().stream();
+                return Flux.fromStream(outboxSupplier.get().stream());
             } else if (aRole.equals(Role.DRAFTS)) {
-                return draftsSupplier.get().stream();
+                return Flux.fromStream(draftsSupplier.get().stream());
             }
-            return Stream.empty();
+            return Flux.empty();
         }
     }
 


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


[james-project] 05/05: JAMES-3376 Demonstrate INBOX content loading with back-references

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

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

commit f5be35258eec3340ad73792ea94c07604a2c15bf
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Sep 17 11:39:20 2020 +0700

    JAMES-3376 Demonstrate INBOX content loading with back-references
---
 .../rfc8621/contract/BackReferenceContract.scala   | 126 +++++++++++++++++++++
 1 file changed, 126 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
index 449e091..8207ad6 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
@@ -18,13 +18,21 @@
  ****************************************************************/
 package org.apache.james.jmap.rfc8621.contract
 
+import java.nio.charset.StandardCharsets
+
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured._
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import net.javacrumbs.jsonunit.core.Option
+import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture._
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.{MailboxId, MailboxPath, MessageId}
+import org.apache.james.mime4j.dom.Message
+import org.apache.james.modules.MailboxProbeImpl
 import org.apache.james.utils.DataProbeImpl
 import org.junit.jupiter.api.{BeforeEach, Test}
 
@@ -400,4 +408,122 @@ trait BackReferenceContract {
          |    ]
          |}""".stripMargin)
   }
+
+  @Test
+  def loadingAccountWithBackReferences(server: GuiceJamesServer): Unit = {
+    val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: MailboxId = mailboxProbe
+      .createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(Message.Builder
+        .of.setSubject("message 1")
+        .setBody("testmail", StandardCharsets.UTF_8)
+        .build))
+      .getMessageId
+    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(Message.Builder
+        .of.setSubject("message 2")
+        .setBody("testmail", StandardCharsets.UTF_8)
+        .build))
+      .getMessageId
+    val messageId3: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(Message.Builder
+        .of.setSubject("message 3")
+        .setBody("testmail", StandardCharsets.UTF_8)
+        .build))
+      .getMessageId
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:mail"],
+           |  "methodCalls": [[
+           |    "Mailbox/query",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "filter": {"role":"Inbox"}
+           |    },
+           |    "c1"],[
+           |    "Email/query",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "filter": {
+           |        "#inMailbox": {
+           |         "resultOf":"c1",
+           |         "name":"Mailbox/query",
+           |         "path":"ids[0]"
+           |       }
+           |      }
+           |    },
+           |    "c2"], [
+           |     "Email/get",
+           |     {
+           |       "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |       "properties": ["id", "subject"],
+           |       "#ids": {
+           |         "resultOf":"c2",
+           |         "name":"Email/query",
+           |         "path":"ids/*"
+           |       }
+           |     },
+           |     "c3"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .extract()
+      .body()
+      .asString()
+
+    assertThatJson(response)
+      .withOptions(new Options(Option.IGNORING_ARRAY_ORDER))
+      .isEqualTo(
+      s"""{
+         |    "sessionState": "75128aab4b1b",
+         |    "methodResponses": [
+         |        [
+         |            "Mailbox/query",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "queryState": "33cc79f8",
+         |                "canCalculateChanges": false,
+         |                "ids": ["${mailboxId.serialize}"],
+         |                "position": 0,
+         |                "limit": 256
+         |            },
+         |            "c1"
+         |        ],
+         |        [
+         |            "Email/query",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "queryState": "a6904f19",
+         |                "canCalculateChanges": false,
+         |                "ids": ["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"],
+         |                "position": 0,
+         |                "limit": 256
+         |            },
+         |            "c2"
+         |        ],
+         |        [
+         |            "Email/get",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "state": "000001",
+         |                "list": [
+         |                    {"id": "${messageId3.serialize}", "subject": "message 3"},
+         |                    {"id": "${messageId2.serialize}", "subject": "message 2"},
+         |                    {"id": "${messageId1.serialize}", "subject": "message 1"}
+         |                ],
+         |                "notFound": []
+         |            },
+         |            "c3"
+         |        ]
+         |    ]
+         |}""".stripMargin)
+  }
 }


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


[james-project] 03/05: JAMES-3376 Implement Mailbox/query to retrieve mailbox by role

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

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

commit 7149fa824f73422a8e46913f797be23ce08dd89e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Sep 16 17:56:32 2020 +0700

    JAMES-3376 Implement Mailbox/query to retrieve mailbox by role
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   4 +-
 .../DistributedMailboxQueryMethodTest.java         |  55 +++
 .../contract/MailboxQueryMethodContract.scala      | 449 +++++++++++++++++++++
 .../memory/MemoryMailboxQueryMethodTest.java       |  38 ++
 .../james/jmap/json/EmailQuerySerializer.scala     |   3 +-
 ...rializer.scala => MailboxQuerySerializer.scala} |  32 +-
 .../org/apache/james/jmap/mail/EmailQuery.scala    |  22 +-
 .../mail/{EmailQuery.scala => MailboxQuery.scala}  |  36 +-
 .../james/jmap/method/EmailQueryMethod.scala       |   4 +-
 ...lQueryMethod.scala => MailboxQueryMethod.scala} |  61 ++-
 .../{mail/EmailQuery.scala => model/Query.scala}   |  29 +-
 11 files changed, 611 insertions(+), 122 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 3f29127..38214f5 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -37,6 +37,7 @@ import org.apache.james.jmap.method.CoreEchoMethod;
 import org.apache.james.jmap.method.EmailGetMethod;
 import org.apache.james.jmap.method.EmailQueryMethod;
 import org.apache.james.jmap.method.MailboxGetMethod;
+import org.apache.james.jmap.method.MailboxQueryMethod;
 import org.apache.james.jmap.method.MailboxSetMethod;
 import org.apache.james.jmap.method.Method;
 import org.apache.james.jmap.method.SystemZoneIdProvider;
@@ -68,9 +69,10 @@ public class RFC8621MethodsModule extends AbstractModule {
         Multibinder<Method> methods = Multibinder.newSetBinder(binder(), Method.class);
         methods.addBinding().to(CoreEchoMethod.class);
         methods.addBinding().to(MailboxGetMethod.class);
+        methods.addBinding().to(MailboxQueryMethod.class);
+        methods.addBinding().to(MailboxSetMethod.class);
         methods.addBinding().to(EmailGetMethod.class);
         methods.addBinding().to(EmailQueryMethod.class);
-        methods.addBinding().to(MailboxSetMethod.class);
         methods.addBinding().to(VacationResponseGetMethod.class);
         methods.addBinding().to(VacationResponseSetMethod.class);
     }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxQueryMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxQueryMethodTest.java
new file mode 100644
index 0000000..c81a477
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxQueryMethodTest.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.rfc8621.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.MailboxQueryMethodContract;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DistributedMailboxQueryMethodTest implements MailboxQueryMethodContract {
+    public static final DockerElasticSearchExtension ELASTIC_SEARCH_EXTENSION = new DockerElasticSearchExtension();
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                    .s3()
+                    .disableCache()
+                    .deduplication())
+            .build())
+        .extension(ELASTIC_SEARCH_EXTENSION)
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
new file mode 100644
index 0000000..fe714be
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
@@ -0,0 +1,449 @@
+/****************************************************************
+ * 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.rfc8621.contract
+
+import com.google.common.hash.Hashing
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath}
+import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
+import org.apache.james.utils.DataProbeImpl
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+trait MailboxQueryMethodContract {
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString, ANDRE_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
+  }
+
+  @Test
+  def roleShouldAllowToRetrieveTheInbox(server: GuiceJamesServer): Unit = {
+    val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, "INBOX"))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":"Inbox"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "Mailbox/query",
+           |            {
+           |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(mailboxId)}",
+           |                "canCalculateChanges": false,
+           |                "ids": [
+           |                    "${mailboxId.serialize}"
+           |                ],
+           |                "position": 0,
+           |                "limit": 256
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def roleShouldAllowToRetrieveTheInboxUponCaseVariation(server: GuiceJamesServer): Unit = {
+    val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, "InBoX"))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":"Inbox"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+      .inPath("methodResponses[0][1].ids")
+        .isEqualTo(s"""[
+           |  "${mailboxId.serialize}"
+           |]""".stripMargin)
+  }
+
+  @Test
+  def queryByRoleShouldNotReturnDelegatedMailboxes(server: GuiceJamesServer): Unit = {
+    val andreInbox = MailboxPath.inbox(ANDRE)
+    val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(andreInbox)
+    val bobInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.inbox(BOB))
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreInbox, BOB.asString, MailboxACL.FULL_RIGHTS)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":"Inbox"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "Mailbox/query",
+           |            {
+           |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(bobInboxId)}",
+           |                "canCalculateChanges": false,
+           |                "ids": [
+           |                    "${bobInboxId.serialize}"
+           |                ],
+           |                "position": 0,
+           |                "limit": 256
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def queryByRoleShouldNotReturnDelegatedMailboxesWhenCaseVariation(server: GuiceJamesServer): Unit = {
+    val andreInbox = MailboxPath.inbox(ANDRE)
+    val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(andreInbox)
+    val bobInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, "InBoX"))
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreInbox, BOB.asString, MailboxACL.FULL_RIGHTS)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":"Inbox"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "Mailbox/query",
+           |            {
+           |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(bobInboxId)}",
+           |                "canCalculateChanges": false,
+           |                "ids": [
+           |                    "${bobInboxId.serialize}"
+           |                ],
+           |                "position": 0,
+           |                "limit": 256
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def invalidRoleShouldBeRejected(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":"Invalid"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "{\\"errors\\":[{\\"path\\":\\"obj.filter.role\\",\\"messages\\":[\\"Invalid is not a valid role\\"]}]}"
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def roleShouldBeAString(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"role":123}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "{\\"errors\\":[{\\"path\\":\\"obj.filter.role\\",\\"messages\\":[\\"Expecting a JsString to be representing a role\\"]}]}"
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}
+           |""".stripMargin)
+  }
+
+  @Test
+  def roleShouldBeCompulsory(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "{\\"errors\\":[{\\"path\\":\\"obj.filter.role\\",\\"messages\\":[\\"error.path.missing\\"]}]}"
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def filterShouldBeCompulsory(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "{\\"errors\\":[{\\"path\\":\\"obj.filter\\",\\"messages\\":[\\"error.path.missing\\"]}]}"
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}
+           |""".stripMargin)
+  }
+
+  private def generateQueryState(ids: MailboxId*): String =
+    Hashing.murmur3_32()
+      .hashUnencodedChars(ids.toList.map(_.serialize()).mkString(" "))
+      .toString
+}
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxQueryMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxQueryMethodTest.java
new file mode 100644
index 0000000..e20c5fd
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxQueryMethodTest.java
@@ -0,0 +1,38 @@
+/****************************************************************
+ * 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.rfc8621.memory;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.MailboxQueryMethodContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryMailboxQueryMethodTest implements MailboxQueryMethodContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
index 6c7cfb8..335f574 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
@@ -20,7 +20,7 @@
 package org.apache.james.jmap.json
 
 import javax.inject.Inject
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition, Limit, Position, QueryState}
+import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition}
 import org.apache.james.jmap.model._
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
@@ -39,6 +39,7 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
 
   private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition]
   private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
+  private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = Json.valueWrites[CanCalculateChange]
   private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
   private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
similarity index 60%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
index 6c7cfb8..f8580c9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
@@ -19,34 +19,36 @@
 
 package org.apache.james.jmap.json
 
-import javax.inject.Inject
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition, Limit, Position, QueryState}
+import org.apache.james.jmap.mail.{MailboxFilter, MailboxQueryRequest, MailboxQueryResponse}
 import org.apache.james.jmap.model._
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import org.apache.james.mailbox.Role
+import org.apache.james.mailbox.model.MailboxId
 import play.api.libs.json._
 
+import scala.jdk.OptionConverters._
 import scala.language.implicitConversions
-import scala.util.Try
 
-class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
+object MailboxQuerySerializer {
   private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
+  private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = Json.valueWrites[CanCalculateChange]
 
   private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
-  private implicit val mailboxIdReads: Reads[MailboxId] = {
-    case JsString(serializedMailboxId) => Try(JsSuccess(mailboxIdFactory.fromString(serializedMailboxId))).getOrElse(JsError())
-    case _ => JsError()
-  }
 
-  private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition]
-  private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
+
+  private implicit val roleReads: Reads[Role] = {
+    case JsString(value) => Role.from(value).toScala.map(JsSuccess(_))
+      .getOrElse(JsError(s"$value is not a valid role"))
+    case _ => JsError("Expecting a JsString to be representing a role")
+  }
+  private implicit val filterReads: Reads[MailboxFilter] = Json.reads[MailboxFilter]
+  private implicit val emailQueryRequestReads: Reads[MailboxQueryRequest] = Json.reads[MailboxQueryRequest]
   private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
   private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
-  private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
 
-  private implicit def emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
+  private implicit def mailboxQueryResponseWrites: OWrites[MailboxQueryResponse] = Json.writes[MailboxQueryResponse]
 
-  def serialize(emailQueryResponse: EmailQueryResponse): JsObject = Json.toJsObject(emailQueryResponse)
+  def serialize(mailboxQueryResponse: MailboxQueryResponse): JsObject = Json.toJsObject(mailboxQueryResponse)
 
-  def deserializeEmailQueryRequest(input: JsValue): JsResult[EmailQueryRequest] = Json.fromJson[EmailQueryRequest](input)
+  def deserialize(input: JsValue): JsResult[MailboxQueryRequest] = Json.fromJson[MailboxQueryRequest](input)
 }
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 5f051cc..d5a0302 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
@@ -19,8 +19,7 @@
 
 package org.apache.james.jmap.mail
 
-import com.google.common.hash.Hashing
-import org.apache.james.jmap.model.{AccountId, UTCDate}
+import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Limit, Position, QueryState, UTCDate}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 
 case class FilterCondition(inMailbox: Option[MailboxId],
@@ -30,26 +29,9 @@ case class FilterCondition(inMailbox: Option[MailboxId],
 
 case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition])
 
-case class Position(value: Int) extends AnyVal
-object Position{
-  val zero: Position = Position(0)
-}
-case class Limit(value: Long) extends AnyVal
-object Limit {
-  val default: Limit = Limit(256L)
-}
-case class QueryState(value: String) extends AnyVal
-
-object QueryState {
-  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
-    Hashing.murmur3_32()
-      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
-      .toString)
-}
-
 case class EmailQueryResponse(accountId: AccountId,
                               queryState: QueryState,
-                              canCalculateChanges: Boolean,
+                              canCalculateChanges: CanCalculateChange,
                               ids: Seq[MessageId],
                               position: Position,
                               limit: Option[Limit])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
similarity index 56%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
index 5f051cc..56a7e62 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
@@ -19,37 +19,17 @@
 
 package org.apache.james.jmap.mail
 
-import com.google.common.hash.Hashing
-import org.apache.james.jmap.model.{AccountId, UTCDate}
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Limit, Position, QueryState}
+import org.apache.james.mailbox.Role
+import org.apache.james.mailbox.model.MailboxId
 
-case class FilterCondition(inMailbox: Option[MailboxId],
-                           inMailboxOtherThan: Option[Seq[MailboxId]],
-                           before: Option[UTCDate],
-                           after: Option[UTCDate])
+case class MailboxQueryRequest(accountId: AccountId, filter: MailboxFilter)
 
-case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition])
+case class MailboxFilter(role: Role)
 
-case class Position(value: Int) extends AnyVal
-object Position{
-  val zero: Position = Position(0)
-}
-case class Limit(value: Long) extends AnyVal
-object Limit {
-  val default: Limit = Limit(256L)
-}
-case class QueryState(value: String) extends AnyVal
-
-object QueryState {
-  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
-    Hashing.murmur3_32()
-      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
-      .toString)
-}
-
-case class EmailQueryResponse(accountId: AccountId,
+case class MailboxQueryResponse(accountId: AccountId,
                               queryState: QueryState,
-                              canCalculateChanges: Boolean,
-                              ids: Seq[MessageId],
+                              canCalculateChanges: CanCalculateChange,
+                              ids: Seq[MailboxId],
                               position: Position,
                               limit: Option[Limit])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index 1fe9ef9..32cbe3f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -23,7 +23,7 @@ import java.util.Date
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
+import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
@@ -66,7 +66,7 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
       .collectSeq()
       .map(ids => EmailQueryResponse(accountId = request.accountId,
         queryState = QueryState.forIds(ids),
-        canCalculateChanges = false,
+        canCalculateChanges = CanCalculateChange(false),
         ids = ids,
         position = Position.zero,
         limit = Some(Limit.default)))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
similarity index 54%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
index 1fe9ef9..a85b657 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
@@ -18,72 +18,59 @@
  ****************************************************************/
 package org.apache.james.jmap.method
 
-import java.util.Date
-
 import eu.timepit.refined.auto._
 import javax.inject.Inject
-import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
+import org.apache.james.jmap.json.{MailboxQuerySerializer, ResponseSerializer}
+import org.apache.james.jmap.mail.{MailboxQueryRequest, MailboxQueryResponse}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model._
 import org.apache.james.jmap.routes.ProcessingContext
-import org.apache.james.mailbox.exception.MailboxNotFoundException
-import org.apache.james.jmap.utils.search.MailboxFilter
-import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
-import org.apache.james.mailbox.model.SearchQuery.{Conjunction, ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, InternalDateCriterion}
-import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
-import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
-import org.apache.james.mailbox.{MailboxManager, MailboxSession}
+import org.apache.james.mailbox.{MailboxSession, SystemMailboxesProvider}
 import org.apache.james.metrics.api.MetricFactory
 import org.reactivestreams.Publisher
 import play.api.libs.json.{JsError, JsSuccess}
 import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
 
-import scala.collection.JavaConverters.seqAsJavaListConverter
-
-class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
-                                  mailboxManager: MailboxManager,
-                                  metricFactory: MetricFactory) extends Method {
-  override val methodName = MethodName("Email/query")
+class MailboxQueryMethod @Inject()(systemMailboxesProvider: SystemMailboxesProvider,
+                                   metricFactory: MetricFactory) extends Method {
+  override val methodName = MethodName("Mailbox/query")
   override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
 
   override def process(capabilities: Set[CapabilityIdentifier], invocation: Invocation, mailboxSession: MailboxSession, processingContext: ProcessingContext): Publisher[(Invocation, ProcessingContext)] =
     metricFactory.decoratePublisherWithTimerMetricLogP99(JMAP_RFC8621_PREFIX + methodName.value,
-      asEmailQueryRequest(invocation.arguments)
+      asMailboxQueryRequest(invocation.arguments)
         .flatMap(processRequest(mailboxSession, invocation, _))
         .onErrorResume {
-          case e: IllegalArgumentException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId))
-          case e: MailboxNotFoundException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId))
+          case e: IllegalArgumentException => SMono.just(
+            Invocation.error(
+              errorCode = ErrorCode.InvalidArguments,
+              description = e.getMessage,
+              methodCallId = invocation.methodCallId))
           case e: Throwable => SMono.raiseError(e)
         }
         .map(invocationResult => (invocationResult, processingContext)))
 
-  private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = {
-    val searchQuery = searchQueryFromRequest(request)
-    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, Limit.default.value))
+  private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: MailboxQueryRequest): SMono[Invocation] = {
+    SFlux.fromPublisher(systemMailboxesProvider.getMailboxByRole(request.filter.role, mailboxSession.getUser))
+      .map(_.getId)
       .collectSeq()
-      .map(ids => EmailQueryResponse(accountId = request.accountId,
-        queryState = QueryState.forIds(ids),
-        canCalculateChanges = false,
+      .map(ids => MailboxQueryResponse(accountId = request.accountId,
+        queryState = QueryState.forMailboxIds(ids),
+        canCalculateChanges = CanCalculateChange(false),
         ids = ids,
         position = Position.zero,
         limit = Some(Limit.default)))
-      .map(response => Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId))
-  }
-
-  private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = {
-    val query = QueryFilter.buildQuery(request)
-    val defaultSort = new SearchQuery.Sort(SortClause.Arrival, SearchQuery.Sort.Order.REVERSE)
-    val querySorted = query.sorts(defaultSort)
-
-    MailboxFilter.buildQuery(request, querySorted.build())
+      .map(response => Invocation(methodName = methodName, arguments = Arguments(MailboxQuerySerializer.serialize(response)), methodCallId = invocation.methodCallId))
+      .subscribeOn(Schedulers.elastic())
   }
 
-  private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] =
-    serializer.deserializeEmailQueryRequest(arguments.value) match {
+  private def asMailboxQueryRequest(arguments: Arguments): SMono[MailboxQueryRequest] =
+    MailboxQuerySerializer.deserialize(arguments.value) match {
       case JsSuccess(emailQueryRequest, _) => SMono.just(emailQueryRequest)
       case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
     }
+
 }
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
similarity index 65%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
index 5f051cc..24135dd 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
@@ -17,19 +17,11 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.mail
+package org.apache.james.jmap.model
 
 import com.google.common.hash.Hashing
-import org.apache.james.jmap.model.{AccountId, UTCDate}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 
-case class FilterCondition(inMailbox: Option[MailboxId],
-                           inMailboxOtherThan: Option[Seq[MailboxId]],
-                           before: Option[UTCDate],
-                           after: Option[UTCDate])
-
-case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition])
-
 case class Position(value: Int) extends AnyVal
 object Position{
   val zero: Position = Position(0)
@@ -38,18 +30,19 @@ case class Limit(value: Long) extends AnyVal
 object Limit {
   val default: Limit = Limit(256L)
 }
-case class QueryState(value: String) extends AnyVal
 
+case class QueryState(value: String) extends AnyVal
 object QueryState {
-  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
+  def forIds(ids: Seq[MessageId]): QueryState =
+    forStrings(ids.map(_.serialize()))
+
+  def forMailboxIds(ids: Seq[MailboxId]): QueryState =
+    forStrings(ids.map(_.serialize()))
+
+  def forStrings(strings: Seq[String]): QueryState = QueryState(
     Hashing.murmur3_32()
-      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
+      .hashUnencodedChars(strings.mkString(" "))
       .toString)
 }
 
-case class EmailQueryResponse(accountId: AccountId,
-                              queryState: QueryState,
-                              canCalculateChanges: Boolean,
-                              ids: Seq[MessageId],
-                              position: Position,
-                              limit: Option[Limit])
+case class CanCalculateChange(value: Boolean) extends AnyVal


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