You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by rc...@apache.org on 2020/06/08 03:12:31 UTC

[james-project] 10/16: JAMES-3171 Port MailboxFactory to jmap-rfc8621

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 24482cc489fbeb65be1cf57de51dd63390fc4f14
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu May 21 10:18:20 2020 +0700

    JAMES-3171 Port MailboxFactory to jmap-rfc8621
---
 .../scala/org/apache/james/jmap/mail/Mailbox.scala |  48 ++++++--
 .../apache/james/jmap/model/MailboxFactory.scala   | 125 +++++++++++++++++++++
 .../james/jmap/json/MailboxSerializationTest.scala |   2 +-
 3 files changed, 162 insertions(+), 13 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Mailbox.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Mailbox.scala
index aaff40e..fc03a7b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Mailbox.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Mailbox.scala
@@ -19,11 +19,12 @@
 
 package org.apache.james.jmap.mail
 
+import eu.timepit.refined
 import eu.timepit.refined.api.Refined
 import eu.timepit.refined.auto._
 import eu.timepit.refined.collection.NonEmpty
 import org.apache.james.core.Username
-import org.apache.james.jmap.mail.Mailbox.MailboxName
+import org.apache.james.jmap.mail.MailboxName.MailboxName
 import org.apache.james.jmap.model.UnsignedInt.UnsignedInt
 import org.apache.james.mailbox.Role
 import org.apache.james.mailbox.model.MailboxId
@@ -38,15 +39,29 @@ case class MayRename(value: Boolean) extends AnyVal
 case class MayDelete(value: Boolean) extends AnyVal
 case class MaySubmit(value: Boolean) extends AnyVal
 
+object MailboxRights {
+  val FULL: MailboxRights = MailboxRights(
+    mayReadItems = MayReadItems(true),
+    mayAddItems = MayAddItems(true),
+    mayRemoveItems = MayRemoveItems(true),
+    maySetSeen = MaySetSeen(true),
+    maySetKeywords = MaySetKeywords(true),
+    mayCreateChild = MayCreateChild(true),
+    mayRename = MayRename(true),
+    mayDelete = MayDelete(true),
+    maySubmit = MaySubmit(true),
+  )
+}
+
 case class MailboxRights(mayReadItems: MayReadItems,
-                               mayAddItems: MayAddItems,
-                               mayRemoveItems: MayRemoveItems,
-                               maySetSeen: MaySetSeen,
-                               maySetKeywords: MaySetKeywords,
-                               mayCreateChild: MayCreateChild,
-                               mayRename: MayRename,
-                               mayDelete: MayDelete,
-                               maySubmit: MaySubmit)
+                         mayAddItems: MayAddItems,
+                         mayRemoveItems: MayRemoveItems,
+                         maySetSeen: MaySetSeen,
+                         maySetKeywords: MaySetKeywords,
+                         mayCreateChild: MayCreateChild,
+                         mayRename: MayRename,
+                         mayDelete: MayDelete,
+                         maySubmit: MaySubmit)
 
 object MailboxNamespace {
   def delegated(owner: Username) = DelegatedNamespace(owner)
@@ -61,6 +76,8 @@ case class PersonalNamespace() extends MailboxNamespace
 case class DelegatedNamespace(owner: Username) extends MailboxNamespace
 
 object SortOrder {
+  val defaultSortOrder: SortOrder = SortOrder(1000L)
+
   private val defaultSortOrders = Map(
       Role.INBOX -> SortOrder(10L),
       Role.ARCHIVE -> SortOrder(20L),
@@ -71,7 +88,7 @@ object SortOrder {
       Role.SPAM -> SortOrder(70L),
       Role.TEMPLATES -> SortOrder(80L),
       Role.RESTORED_MESSAGES -> SortOrder(90L))
-    .withDefaultValue(SortOrder(1000L))
+    .withDefaultValue(defaultSortOrder)
 
   def getSortOrder(role: Role): SortOrder = defaultSortOrders(role)
 }
@@ -97,8 +114,15 @@ sealed trait QuotasExtension extends MailboxExtensionAdditionalFields {
   def quotas: Quotas
 }
 
-object Mailbox {
-  type MailboxName = String Refined NonEmpty
+object MailboxName {
+  type MailboxNameConstraint = NonEmpty
+  type MailboxName = String Refined MailboxNameConstraint
+
+  def liftOrThrow(value: String): MailboxName =
+    refined.refineV[MailboxNameConstraint](value) match {
+      case scala.util.Right(value) => value
+      case Left(error) => throw new IllegalArgumentException(error)
+    }
 }
 
 case class Mailbox(id: MailboxId,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala
new file mode 100644
index 0000000..caf9046
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala
@@ -0,0 +1,125 @@
+/****************************************************************
+ * 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.model
+
+import javax.inject.Inject
+import org.apache.james.jmap.mail.MailboxName.MailboxName
+import org.apache.james.jmap.mail._
+import org.apache.james.jmap.utils.quotas.QuotaLoader
+import org.apache.james.mailbox.model.MailboxACL.EntryKey
+import org.apache.james.mailbox.model.{MailboxCounters, MailboxId, MailboxMetaData, MailboxPath, MailboxACL => JavaMailboxACL}
+import org.apache.james.mailbox.{MailboxSession, Role, SubscriptionManager}
+import reactor.core.scala.publisher.SMono
+
+import scala.jdk.CollectionConverters._
+import scala.jdk.OptionConverters._
+
+sealed trait MailboxConstructionOrder
+
+class Factory
+
+class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager) {
+
+  def create(mailboxMetaData: MailboxMetaData,
+             mailboxSession: MailboxSession,
+             allMailboxesMetadata: Seq[MailboxMetaData],
+             quotaLoader: QuotaLoader): SMono[Mailbox] = {
+
+    val id: MailboxId = mailboxMetaData.getId
+
+    val name: MailboxName = MailboxName.liftOrThrow(mailboxMetaData.getPath
+      .getName
+      .split(mailboxSession.getPathDelimiter)
+      .last)
+
+    val role: Option[Role] = Role.from(mailboxMetaData.getPath.getName)
+      .filter(_ => mailboxMetaData.getPath.belongsTo(mailboxSession)).toScala
+    val sortOrder: SortOrder = role.map(SortOrder.getSortOrder).getOrElse(SortOrder.defaultSortOrder)
+    val quotas: SMono[Quotas] = quotaLoader.getQuotas(mailboxMetaData.getPath)
+    val rights: Rights = Rights.fromACL(MailboxACL.fromJava(mailboxMetaData.getResolvedAcls))
+
+    val sanitizedCounters: MailboxCounters = mailboxMetaData.getCounters.sanitize()
+    val unreadEmails: UnreadEmails = UnreadEmails(UnsignedInt.liftOrThrow(sanitizedCounters.getUnseen))
+    val unreadThreads: UnreadThreads = UnreadThreads(UnsignedInt.liftOrThrow(sanitizedCounters.getUnseen))
+    val totalEmails: TotalEmails = TotalEmails(UnsignedInt.liftOrThrow(sanitizedCounters.getCount))
+    val totalThreads: TotalThreads = TotalThreads(UnsignedInt.liftOrThrow(sanitizedCounters.getCount))
+
+    val isOwner = mailboxMetaData.getPath.belongsTo(mailboxSession)
+    val aclEntryKey: EntryKey = EntryKey.createUserEntryKey(mailboxSession.getUser)
+
+    val namespace: MailboxNamespace = if (isOwner) {
+      PersonalNamespace()
+    } else {
+      DelegatedNamespace(mailboxMetaData.getPath.getUser)
+    }
+
+    val parentPath: Option[MailboxPath] =
+      mailboxMetaData.getPath
+        .getHierarchyLevels(mailboxSession.getPathDelimiter)
+        .asScala
+        .reverse
+        .drop(1)
+        .headOption
+
+    val parentId: Option[MailboxId] = allMailboxesMetadata.filter(otherMetadata => parentPath.contains(otherMetadata.getPath))
+      .map(_.getId)
+      .headOption
+
+    val myRights: MailboxRights = if (isOwner) {
+      MailboxRights.FULL
+    } else {
+      val rights = Rfc4314Rights.fromJava(mailboxMetaData.getResolvedAcls
+        .getEntries
+        .getOrDefault(aclEntryKey, JavaMailboxACL.NO_RIGHTS))
+        .toRights
+      MailboxRights(
+        mayReadItems = MayReadItems(rights.contains(Right.Read)),
+        mayAddItems = MayAddItems(rights.contains(Right.Insert)),
+        mayRemoveItems = MayRemoveItems(rights.contains(Right.DeleteMessages)),
+        maySetSeen = MaySetSeen(rights.contains(Right.Seen)),
+        maySetKeywords = MaySetKeywords(rights.contains(Right.Write)),
+        mayCreateChild = MayCreateChild(false),
+        mayRename = MayRename(false),
+        mayDelete = MayDelete(false),
+        maySubmit = MaySubmit(false))
+    }
+
+    def retrieveIsSubscribed: IsSubscribed = IsSubscribed(subscriptionManager
+      .subscriptions(mailboxSession)
+      .contains(mailboxMetaData.getPath.getName))
+
+    SMono.fromPublisher(quotas)
+      .map(quotas => Mailbox(
+        id = id,
+        name = name,
+        parentId = parentId,
+        role = role,
+        sortOrder = sortOrder,
+        unreadEmails = unreadEmails,
+        totalEmails = totalEmails,
+        unreadThreads = unreadThreads,
+        totalThreads = totalThreads,
+        myRights = myRights,
+        namespace = namespace,
+        rights = rights,
+        quotas = quotas,
+        isSubscribed = retrieveIsSubscribed))
+  }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
index 940d29a..c5b49d8 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
@@ -23,7 +23,7 @@ import eu.timepit.refined.auto._
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.james.core.{Domain, Username}
 import org.apache.james.jmap.json.MailboxSerializationTest.MAILBOX
-import org.apache.james.jmap.mail.Mailbox.MailboxName
+import org.apache.james.jmap.mail.MailboxName.MailboxName
 import org.apache.james.jmap.mail.{IsSubscribed, Mailbox, MailboxNamespace, MailboxRights, MayAddItems, MayCreateChild, MayDelete, MayReadItems, MayRemoveItems, MayRename, MaySetKeywords, MaySetSeen, MaySubmit, PersonalNamespace, Quota, QuotaId, QuotaRoot, Quotas, Right, Rights, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, Value}
 import org.apache.james.mailbox.Role
 import org.apache.james.mailbox.model.{MailboxId, TestId}


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