JAMES-4016 JMAP email keywords in mixed JMAP/IMAP usage
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
index 645298c..b01c6ea 100644
--- a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
@@ -35,6 +35,7 @@
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.MessageUid;
import org.apache.james.mailbox.SubscriptionManager;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.model.ByteSourceContent;
@@ -43,6 +44,7 @@
import org.apache.james.mailbox.model.MailboxMetaData;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.model.search.MailboxQuery;
import org.apache.james.mailbox.model.search.Wildcard;
@@ -166,6 +168,17 @@
}
}
+ public void copy(Username username, MailboxPath source, MailboxPath destination, MessageUid uid) throws MailboxException {
+ MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
+ mailboxManager.copyMessages(MessageRange.one(uid), source, destination, mailboxSession);
+ }
+
+ public void setFlags(Username username, MailboxPath mailboxPath, MessageUid uid, Flags flags) throws MailboxException {
+ MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
+ MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, mailboxSession);
+ messageManager.setFlags(flags, MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(uid), mailboxSession);
+ }
+
public ComposedMessageId appendMessage(String username, MailboxPath mailboxPath, MessageManager.AppendCommand appendCommand)
throws MailboxException {
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/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index ddd8ba8..397e759 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -39,14 +39,14 @@
import org.apache.james.jmap.api.model.AccountId
import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
import org.apache.james.jmap.core.UuidState.INSTANCE
-import org.apache.james.jmap.draft.JmapGuiceProbe
+import org.apache.james.jmap.draft.{JmapGuiceProbe}
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.EmailGetMethodContract.createTestMessage
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ALICE, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbe
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
-import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageId}
+import org.apache.james.mailbox.model.{ComposedMessageId, MailboxACL, MailboxId, MailboxPath, MessageId}
import org.apache.james.mime4j.dom.Message
import org.apache.james.mime4j.message.MultipartBuilder
import org.apache.james.mime4j.stream.RawField
@@ -6766,6 +6766,72 @@
}
@Test
+ def shouldAggregateKeywordsAccrossMailbox(server: GuiceJamesServer): Unit = {
+ val message: Message = createTestMessage
+
+ val flags1: Flags = new Flags(Flags.Flag.ANSWERED)
+ flags1.add(Flags.Flag.FLAGGED)
+ flags1.add("f1")
+ flags1.add("f2")
+
+ val flags2: Flags = new Flags(Flags.Flag.SEEN)
+ flags2.add(Flags.Flag.FLAGGED)
+ flags2.add("f3")
+ flags2.add("f2")
+
+ val path1 = MailboxPath.inbox(BOB)
+ val path2 = MailboxPath.forUser(BOB, "box2")
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(path1)
+ mailboxProbe.createMailbox(path2)
+ val messageId: ComposedMessageId = mailboxProbe.appendMessage(BOB.asString(), path1, AppendCommand.builder()
+ .withFlags(flags1)
+ .build(message))
+ mailboxProbe.copy(BOB, path1, path2, messageId.getUid)
+ mailboxProbe.setFlags(BOB, path1, messageId.getUid, flags2)
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.getMessageId.serialize}"],
+ | "properties": ["keywords"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(
+ s"""
+ | {
+ | "id":"${messageId.getMessageId.serialize}",
+ | "keywords": {
+ | "$$flagged": true,
+ | "f1": true,
+ | "f2": true,
+ | "f3": true,
+ | "$$seen": true,
+ | "$$answered": true
+ | }
+ | }
+ """.stripMargin)
+ }
+
+ @Test
def emailGetShouldReturnSpecificHeadersAsRaw(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index 40b2b62..179e627 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -68,6 +68,13 @@
object Email {
private val logger: Logger = LoggerFactory.getLogger(classOf[EmailView])
+ def mergeKeywords(messages: Seq[MessageResult]): Try[Keywords] = {
+ messages.map(_.getFlags)
+ .map(LENIENT_KEYWORDS_FACTORY.fromFlags)
+ .sequence
+ .map(list => list.reduce(_ ++ _))
+ }
+
val defaultCharset = Option(System.getenv("james.jmap.default.charset"))
.map(value => java.nio.charset.Charset.forName(value))
.getOrElse(StandardCharsets.US_ASCII)
@@ -498,7 +505,7 @@
.map(Success(_))
.getOrElse(Failure(new IllegalArgumentException("No message supplied")))
blobId <- BlobId.of(messageId)
- keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
+ keywords <- Email.mergeKeywords(message._2)
} yield {
EmailMetadataView(
metadata = EmailMetadata(
@@ -528,7 +535,7 @@
.getOrElse(Failure(new IllegalArgumentException("No message supplied")))
mime4JMessage <- Email.parseAsMime4JMessage(firstMessage)
blobId <- BlobId.of(messageId)
- keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
+ keywords <- Email.mergeKeywords(message._2)
} yield {
EmailHeaderView(
metadata = EmailMetadata(
@@ -605,7 +612,7 @@
bodyStructure <- EmailBodyPart.of(request.bodyProperties, zoneIdProvider.get(), blobId, mime4JMessage)
bodyValues <- extractBodyValues(htmlTextExtractor)(bodyStructure, request)
preview <- Try(previewFactory.fromMime4JMessage(mime4JMessage))
- keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
+ keywords <- Email.mergeKeywords(message._2)
} yield {
EmailFullView(
metadata = EmailMetadata(
@@ -753,7 +760,7 @@
.getOrElse(Failure(new IllegalArgumentException("No message supplied")))
mime4JMessage <- Email.parseAsMime4JMessage(firstMessage)
blobId <- BlobId.of(messageId)
- keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
+ keywords <- Email.mergeKeywords(message._2)
} yield {
EmailFastView(
metadata = EmailMetadata(
@@ -844,7 +851,7 @@
.getOrElse(Failure(new IllegalArgumentException("No message supplied")))
mime4JMessage <- Email.parseAsMime4JMessage(firstMessage)
blobId <- BlobId.of(messageId)
- keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
+ keywords <- Email.mergeKeywords(message._2)
} yield {
EmailFastViewWithAttachments(
metadata = EmailMetadata(