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 e4395c2d3622b58690082d8580e6366395924956
Author: Raphael Ouazana <[email protected]>
AuthorDate: Fri Sep 11 11:37:34 2020 +0200

    JAMES-3375 implements inMailboxOtherThan filter
---
 .../contract/EmailQueryMethodContract.scala        | 211 +++++++++++++++++++++
 .../org/apache/james/jmap/mail/EmailQuery.scala    |   2 +-
 .../james/jmap/method/EmailQueryMethod.scala       |   9 +-
 .../james/jmap/utils/search/MailboxFilter.scala    |  52 +++++
 4 files changed, 266 insertions(+), 8 deletions(-)

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/EmailQueryMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index 312388f..21a3aad 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -418,6 +418,217 @@ trait EmailQueryMethodContract {
     }
   }
 
+  @Test
+  def shouldListMailsNotInASpecificUserMailboxes(server: GuiceJamesServer): 
Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .getMessageId
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailboxOtherThan": [ "${otherMailboxId.serialize()}" ]
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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"""["${messageId1.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def shouldListMailsNotInZeroMailboxes(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .getMessageId
+    Thread.sleep(1000)
+    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailboxOtherThan": [  ]
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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"""["${messageId2.serialize()}", 
"${messageId1.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def 
listMailsInAFirstMailboxAndNotSomeOtherMailboxShouldReturnMailsInFirstMailbox(server:
 GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val inbox = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .getMessageId
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox":  "${inbox.serialize()}",
+         |        "inMailboxOtherThan": [ "${otherMailboxId.serialize()}" ]
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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"""["${messageId1.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def 
listMailsInAFirstMailboxAndNotInTheSameMailboxShouldReturnEmptyResult(server: 
GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val inbox = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .getMessageId
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter" : {
+         |        "inMailbox":  "${inbox.serialize()}",
+         |        "inMailboxOtherThan": [ "${inbox.serialize()}" ]
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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("[]")
+    }
+  }
+
   private def generateQueryState(messages: MessageId*): String = {
     Hashing.murmur3_32()
       .hashUnencodedChars(messages.toList.map(_.serialize()).mkString(" "))
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 4ac82aa..22c09d0 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
@@ -23,7 +23,7 @@ import com.google.common.hash.Hashing
 import org.apache.james.jmap.model.AccountId
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 
-case class FilterCondition(inMailbox: Option[MailboxId])
+case class FilterCondition(inMailbox: Option[MailboxId], inMailboxOtherThan: 
Option[Seq[MailboxId]])
 
 case class EmailQueryRequest(accountId: AccountId, filter: 
Option[FilterCondition])
 
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 cdb370e..18d391a 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
@@ -28,6 +28,7 @@ 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.mailbox.model.SearchQuery.Sort.SortClause
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession}
@@ -71,13 +72,7 @@ class EmailQueryMethod @Inject() (serializer: 
EmailQuerySerializer,
     val defaultSort = new SearchQuery.Sort(SortClause.Arrival, 
SearchQuery.Sort.Order.REVERSE)
     val querySorted = query.sorts(defaultSort)
 
-    val multiMailboxQueryBuilder = 
MultimailboxesSearchQuery.from(querySorted.build())
-
-    val multiMailboxQueryBuilderWithInMailboxFilter = request.inMailbox match {
-      case Some(mailboxId) => multiMailboxQueryBuilder.inMailboxes(mailboxId)
-      case None => multiMailboxQueryBuilder
-    }
-    multiMailboxQueryBuilderWithInMailboxFilter.build()
+    MailboxFilter.buildQuery(request, querySorted.build())
   }
 
   private def asEmailQueryRequest(arguments: Arguments): 
SMono[EmailQueryRequest] =
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
new file mode 100644
index 0000000..c673e5b
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
@@ -0,0 +1,52 @@
+/****************************************************************
+ * 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.utils.search
+
+import org.apache.james.jmap.mail.EmailQueryRequest
+import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
+
+import scala.collection.JavaConverters.seqAsJavaListConverter
+
+
+sealed trait MailboxFilter {
+  def toQuery(builder: MultimailboxesSearchQuery.Builder, request: 
EmailQueryRequest): MultimailboxesSearchQuery.Builder
+}
+
+case object InMailboxFilter extends MailboxFilter {
+  override def toQuery(builder: MultimailboxesSearchQuery.Builder, request: 
EmailQueryRequest): MultimailboxesSearchQuery.Builder = 
request.filter.flatMap(_.inMailbox) match {
+    case Some(mailboxId) => builder.inMailboxes(mailboxId)
+    case None => builder
+  }
+}
+
+case object NotInMailboxFilter extends MailboxFilter {
+  override def toQuery(builder: MultimailboxesSearchQuery.Builder, request: 
EmailQueryRequest): MultimailboxesSearchQuery.Builder = 
request.filter.flatMap(_.inMailboxOtherThan) match {
+    case Some(mailboxIds) => builder.notInMailboxes(mailboxIds.asJava)
+    case None => builder
+  }
+}
+
+object MailboxFilter {
+  def buildQuery(request: EmailQueryRequest, searchQuery: SearchQuery) = {
+    val multiMailboxQueryBuilder = MultimailboxesSearchQuery.from(searchQuery)
+
+    List(InMailboxFilter, 
NotInMailboxFilter).foldLeft(multiMailboxQueryBuilder)((builder, filter) => 
filter.toQuery(builder, request))
+      .build()
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to