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]
