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 70e18bfe816bc5209edb52bd206e3bbf9737021c
Author: Benoit Tellier <[email protected]>
AuthorDate: Fri Sep 18 13:54:54 2020 +0700

    JAMES-3377 Email/query implement hasAttachment FilterCondition
    
    Also add a test for criterion composition
---
 .../contract/EmailQueryMethodContract.scala        | 220 +++++++++++++++++++++
 .../james/jmap/json/EmailQuerySerializer.scala     |   3 +-
 .../org/apache/james/jmap/mail/EmailQuery.scala    |   3 +-
 .../james/jmap/method/EmailQueryMethod.scala       |   2 +-
 .../james/jmap/utils/search/MailboxFilter.scala    |  23 ++-
 5 files changed, 241 insertions(+), 10 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 d56c851..816b136 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
@@ -91,6 +91,226 @@ trait EmailQueryMethodContract {
   }
 
   @Test
+  def hasAttachmentShouldKeepMessageWithAttachmentWhenTrue(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
+        ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"hasAttachment":true}
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId2.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def hasAttachmentShouldKeepMessageWithoutAttachmentWhenFalse(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
+        ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"hasAttachment":false}
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def queryShouldTakeAllFiltersIntoAccount(server: GuiceJamesServer): Unit = {
+    val beforeRequestDate = 
Date.from(ZonedDateTime.now().minusDays(2).toInstant)
+    val requestDate = ZonedDateTime.now().minusDays(1)
+    val afterRequestDate = Date.from(ZonedDateTime.now().toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate)
+          .build(Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+          .withInternalDate(beforeRequestDate)
+        
.build(ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(afterRequestDate)
+          .build(Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate)
+        
.build(ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "hasAttachment":false,
+         |        "after": 
"${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
+         |       }
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId3)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId3.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
   def shouldListMailsInAllUserMailboxes(server: GuiceJamesServer): Unit = {
     val message: Message = Message.Builder
       .of
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 a4609a6..87d1fc2 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.{Collation, Comparator, EmailQueryRequest, 
EmailQueryResponse, FilterCondition, IsAscending, ReceivedAtSortProperty, 
SortProperty}
+import org.apache.james.jmap.mail.{Collation, Comparator, EmailQueryRequest, 
EmailQueryResponse, FilterCondition, HasAttachment, IsAscending, 
ReceivedAtSortProperty, SortProperty}
 import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, Position, QueryState}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
@@ -48,6 +48,7 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: 
MailboxId.Factory) {
       .fold(JsError(_), JsSuccess(_))
     case _ => JsError("Expecting keywords to be represented by a JsString")
   }
+  private implicit val hasAttachmentReads: Reads[HasAttachment] = 
Json.valueReads[HasAttachment]
   private implicit val filterConditionReads: Reads[FilterCondition] = 
Json.reads[FilterCondition]
   private implicit val limitUnparsedReads: Reads[LimitUnparsed] = 
Json.valueReads[LimitUnparsed]
   private implicit val CanCalculateChangesFormat: Format[CanCalculateChanges] 
= Json.valueFormat[CanCalculateChanges]
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 db196bc..080d526 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
@@ -33,7 +33,8 @@ case class FilterCondition(inMailbox: Option[MailboxId],
                            hasKeyword: Option[Keyword],
                            notKeyword: Option[Keyword],
                            minSize: Option[Size],
-                           maxSize: Option[Size])
+                           maxSize: Option[Size],
+                           hasAttachment: Option[HasAttachment])
 
 case class EmailQueryRequest(accountId: AccountId, limit: 
Option[LimitUnparsed], filter: Option[FilterCondition], comparator: 
Option[Set[Comparator]])
 
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 7437260..e9b541a 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,8 +28,8 @@ import org.apache.james.jmap.model.Invocation.{Arguments, 
MethodName}
 import org.apache.james.jmap.model.{CanCalculateChanges, Capabilities, 
ErrorCode, Invocation, Limit, Position, QueryState}
 import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.jmap.routes.ProcessingContext
-import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.jmap.utils.search.MailboxFilter
+import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.mailbox.exception.MailboxNotFoundException
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession}
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
index 91be2f8..443996b 100644
--- 
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
@@ -21,12 +21,12 @@ package org.apache.james.jmap.utils.search
 import java.util.Date
 
 import org.apache.james.jmap.mail.EmailQueryRequest
-import org.apache.james.mailbox.model.SearchQuery.{Conjunction, 
ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, 
InternalDateCriterion}
+import org.apache.james.mailbox.model.SearchQuery.DateResolution.Second
+import org.apache.james.mailbox.model.SearchQuery.{DateComparator, 
DateOperator, DateResolution, InternalDateCriterion}
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
 
 import scala.jdk.CollectionConverters._
 
-
 sealed trait MailboxFilter {
   def toQuery(builder: MultimailboxesSearchQuery.Builder, request: 
EmailQueryRequest): MultimailboxesSearchQuery.Builder
 }
@@ -53,14 +53,13 @@ object MailboxFilter {
       .build()
   }
 
-
   sealed trait QueryFilter {
     def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): 
SearchQuery.Builder
   }
 
   object QueryFilter {
     def buildQuery(request: EmailQueryRequest): SearchQuery.Builder = {
-      List(ReceivedBefore, ReceivedAfter, HasKeyWord, NotKeyWord, MinSize, 
MaxSize)
+      List(ReceivedBefore, ReceivedAfter, HasAttachment, HasKeyWord, 
NotKeyWord, MinSize, MaxSize)
         .foldLeft(SearchQuery.builder())((builder, filter) => 
filter.toQuery(builder, request))
     }
   }
@@ -68,13 +67,14 @@ object MailboxFilter {
   case object ReceivedBefore extends QueryFilter {
     override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.before) 
match {
       case Some(before) =>
-        val strictlyBefore = new InternalDateCriterion(new 
DateOperator(DateComparator.BEFORE, Date.from(before.asUTC.toInstant), 
DateResolution.Second))
-        val sameDate = new InternalDateCriterion(new 
DateOperator(DateComparator.ON, Date.from(before.asUTC.toInstant), 
DateResolution.Second))
+        val strictlyBefore = 
SearchQuery.internalDateBefore(Date.from(before.asUTC.toInstant), Second)
+        val sameDate = 
SearchQuery.internalDateOn(Date.from(before.asUTC.toInstant), Second)
         builder
-          .andCriteria(new ConjunctionCriterion(Conjunction.OR, 
List[Criterion](strictlyBefore, sameDate).asJava))
+          .andCriteria(SearchQuery.or(strictlyBefore, sameDate))
       case None => builder
     }
   }
+
   case object ReceivedAfter extends QueryFilter {
     override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.after) 
match {
       case Some(after) =>
@@ -85,6 +85,15 @@ object MailboxFilter {
     }
   }
 
+  case object HasAttachment extends QueryFilter {
+    override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =
+      request.filter.flatMap(_.hasAttachment) match {
+        case Some(hasAttachment) => builder
+          .andCriteria(SearchQuery.hasAttachment(hasAttachment.value))
+        case None => builder
+      }
+  }
+
   case object MinSize extends QueryFilter {
     override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.minSize) 
match {
       case Some(minSize) =>


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

Reply via email to