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 672b9101c0b343b126c27ebd0f5dc0d8bf2ba746 Author: Rene Cordier <[email protected]> AuthorDate: Thu Jun 25 11:08:28 2020 +0700 JAMES-3095 Only retrieve requested mailboxes --- .../contract/MailboxGetMethodContract.scala | 2 +- .../org/apache/james/jmap/json/Serializer.scala | 4 +- .../org/apache/james/jmap/mail/MailboxGet.scala | 2 +- .../james/jmap/method/MailboxGetMethod.scala | 41 +++--- .../apache/james/jmap/model/MailboxFactory.scala | 156 +++++++++++++++------ .../jmap/json/MailboxGetSerializationTest.scala | 2 +- 6 files changed, 136 insertions(+), 71 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/MailboxGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala index 1c1a027..b079138 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala @@ -621,7 +621,7 @@ trait MailboxGetMethodContract { } @Test - def getMailboxesByIdsShouldReturnMailboxesInSorteredOrder(server: GuiceJamesServer): Unit = { + def getMailboxesByIdsShouldReturnMailboxesInSortedOrder(server: GuiceJamesServer): Unit = { val mailboxId1: String = server.getProbe(classOf[MailboxProbeImpl]) .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.TRASH)) .serialize diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala index 76c6863..e6d26e3 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala @@ -207,7 +207,9 @@ class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) { private implicit val propertiesRead: Reads[Properties] = Json.valueReads[Properties] private implicit val mailboxGetRequest: Reads[MailboxGetRequest] = Json.reads[MailboxGetRequest] - private implicit val notFoundWrites: Writes[NotFound] = Json.valueWrites[NotFound] + private implicit def notFoundWrites(implicit mailboxIdWrites: Writes[MailboxId]): Writes[NotFound] = + notFound => JsArray(notFound.value.toList.map(mailboxIdWrites.writes)) + private implicit val mailboxGetResponseWrites: Writes[MailboxGetResponse] = Json.writes[MailboxGetResponse] private implicit val jsonValidationErrorWrites: Writes[JsonValidationError] = error => JsString(error.message) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala index a4400ee..5d9b971 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala @@ -32,7 +32,7 @@ case class MailboxGetRequest(accountId: AccountId, ids: Option[Ids], properties: Option[Properties]) -case class NotFound(value: List[MailboxId]) { +case class NotFound(value: Set[MailboxId]) { def merge(other: NotFound): NotFound = NotFound(this.value ++ other.value) } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala index 6eaea11..b3c66c1 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala @@ -27,6 +27,7 @@ import org.apache.james.jmap.model.Invocation.{Arguments, MethodName} import org.apache.james.jmap.model.State.INSTANCE import org.apache.james.jmap.model.{Invocation, MailboxFactory} import org.apache.james.jmap.utils.quotas.{QuotaLoader, QuotaLoaderWithPreloadedDefaultFactory} +import org.apache.james.mailbox.exception.MailboxNotFoundException import org.apache.james.mailbox.model.search.MailboxQuery import org.apache.james.mailbox.model.{MailboxId, MailboxMetaData} import org.apache.james.mailbox.{MailboxManager, MailboxSession} @@ -44,11 +45,11 @@ class MailboxGetMethod @Inject() (serializer: Serializer, override val methodName: MethodName = MethodName("Mailbox/get") object MailboxGetResults { - def found(mailbox: Mailbox): MailboxGetResults = MailboxGetResults(List(mailbox), NotFound(Nil)) - def notFound(mailboxId: MailboxId): MailboxGetResults = MailboxGetResults(Nil, NotFound(List(mailboxId))) + def found(mailbox: Mailbox): MailboxGetResults = MailboxGetResults(Set(mailbox), NotFound(Set.empty)) + def notFound(mailboxId: MailboxId): MailboxGetResults = MailboxGetResults(Set.empty, NotFound(Set(mailboxId))) } - case class MailboxGetResults(mailboxes: List[Mailbox], notFound: NotFound) { + case class MailboxGetResults(mailboxes: Set[Mailbox], notFound: NotFound) { def merge(other: MailboxGetResults): MailboxGetResults = MailboxGetResults(this.mailboxes ++ other.mailboxes, this.notFound.merge(other.notFound)) } @@ -56,11 +57,11 @@ class MailboxGetMethod @Inject() (serializer: Serializer, metricFactory.decoratePublisherWithTimerMetricLogP99(JMAP_RFC8621_PREFIX + methodName.value, asMailboxGetRequest(invocation.arguments) .flatMap(mailboxGetRequest => getMailboxes(mailboxGetRequest, mailboxSession) - .reduce(MailboxGetResults(Nil, NotFound(Nil)), (result1: MailboxGetResults, result2: MailboxGetResults) => result1.merge(result2)) + .reduce(MailboxGetResults(Set.empty, NotFound(Set.empty)), (result1: MailboxGetResults, result2: MailboxGetResults) => result1.merge(result2)) .map(mailboxes => MailboxGetResponse( accountId = mailboxGetRequest.accountId, state = INSTANCE, - list = mailboxes.mailboxes.sortBy(_.sortOrder), + list = mailboxes.mailboxes.toList.sortBy(_.sortOrder), notFound = mailboxes.notFound)) .map(mailboxGetResponse => Invocation( methodName = methodName, @@ -77,18 +78,20 @@ class MailboxGetMethod @Inject() (serializer: Serializer, private def getMailboxes(mailboxGetRequest: MailboxGetRequest, mailboxSession: MailboxSession): SFlux[MailboxGetResults] = mailboxGetRequest.ids match { case None => getAllMailboxes(mailboxSession).map(MailboxGetResults.found) - case Some(ids) => - getAllMailboxes(mailboxSession) - .collectSeq() - .flatMapMany(mailboxes => SFlux.merge(Seq( - SFlux.fromIterable(mailboxes) - .filter(mailbox => ids.value.contains(mailbox.id)) - .map(MailboxGetResults.found), - SFlux.fromIterable(ids.value) - .filter(id => !mailboxes.map(_.id).contains(id)) - .map(MailboxGetResults.notFound)))) + case Some(ids) => SFlux.fromIterable(ids.value) + .flatMap(id => getMailboxResultById(id, mailboxSession)) } + private def getMailboxResultById(mailboxId: MailboxId, mailboxSession: MailboxSession): SMono[MailboxGetResults] = + quotaFactory.loadFor(mailboxSession) + .flatMap(quotaLoader => mailboxFactory.create(mailboxId, mailboxSession, quotaLoader) + .map(MailboxGetResults.found) + .onErrorResume { + case _: MailboxNotFoundException => SMono.just(MailboxGetResults.notFound(mailboxId)) + case error => SMono.raiseError(error) + }) + .subscribeOn(Schedulers.elastic) + private def getAllMailboxes(mailboxSession: MailboxSession): SFlux[Mailbox] = { quotaFactory.loadFor(mailboxSession) .subscribeOn(Schedulers.elastic) @@ -96,7 +99,7 @@ class MailboxGetMethod @Inject() (serializer: Serializer, getAllMailboxesMetaData(mailboxSession).flatMapMany(mailboxesMetaData => SFlux.fromIterable(mailboxesMetaData) .flatMap(mailboxMetaData => - getMailboxOrThrow(mailboxMetaData = mailboxMetaData, + getMailboxResult(mailboxMetaData = mailboxMetaData, mailboxSession = mailboxSession, allMailboxesMetadata = mailboxesMetaData, quotaLoader = quotaLoader)))) @@ -106,7 +109,7 @@ class MailboxGetMethod @Inject() (serializer: Serializer, SFlux.fromPublisher(mailboxManager.search(MailboxQuery.builder.matchesAllMailboxNames.build, mailboxSession)) .collectSeq() - private def getMailboxOrThrow(mailboxSession: MailboxSession, + private def getMailboxResult(mailboxSession: MailboxSession, allMailboxesMetadata: Seq[MailboxMetaData], mailboxMetaData: MailboxMetaData, quotaLoader: QuotaLoader): SMono[Mailbox] = @@ -114,8 +117,4 @@ class MailboxGetMethod @Inject() (serializer: Serializer, mailboxSession = mailboxSession, allMailboxesMetadata = allMailboxesMetadata, quotaLoader = quotaLoader) - .flatMap { - case Left(error) => SMono.raiseError(error) - case scala.Right(mailbox) => SMono.just(mailbox) - } } 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 index 98c3eb7..738b0f9 100644 --- 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 @@ -24,9 +24,9 @@ import org.apache.james.jmap.mail.MailboxName.MailboxName import org.apache.james.jmap.mail._ import org.apache.james.jmap.model.UnsignedInt.UnsignedInt import org.apache.james.jmap.utils.quotas.QuotaLoader +import org.apache.james.mailbox._ 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._ @@ -59,7 +59,7 @@ case class MailboxValidation(mailboxName: MailboxName, totalEmails: TotalEmails, totalThreads: TotalThreads) -class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager) { +class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager, mailboxManager: MailboxManager) { private def retrieveMailboxName(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Either[IllegalArgumentException, MailboxName] = mailboxPath.getName @@ -69,20 +69,63 @@ class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager) { case None => Left(new IllegalArgumentException("No name for the mailbox found")) } + private def getRole(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Option[Role] = Role.from(mailboxPath.getName) + .filter(_ => mailboxPath.belongsTo(mailboxSession)).toScala + + private def getSortOrder(role: Option[Role]): SortOrder = role.map(SortOrder.getSortOrder).getOrElse(SortOrder.defaultSortOrder) + + private def getRights(resolveMailboxACL: JavaMailboxACL): Rights = Rights.fromACL(MailboxACL.fromJava(resolveMailboxACL)) + + private def getNamespace(mailboxPath: MailboxPath, mailboxSession: MailboxSession): MailboxNamespace = mailboxPath.belongsTo(mailboxSession) match { + case true => PersonalNamespace() + case false => DelegatedNamespace(mailboxPath.getUser) + } + + private def getParentPath(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Option[MailboxPath] = mailboxPath + .getHierarchyLevels(mailboxSession.getPathDelimiter) + .asScala + .reverse + .drop(1) + .headOption + + private def aclEntryKey(mailboxSession: MailboxSession): EntryKey = EntryKey.createUserEntryKey(mailboxSession.getUser) + + private def getMyRights(mailboxPath: MailboxPath, resolveMailboxACL: JavaMailboxACL, mailboxSession: MailboxSession): MailboxRights = mailboxPath.belongsTo(mailboxSession) match { + case true => MailboxRights.FULL + case false => + val rights = Rfc4314Rights.fromJava(resolveMailboxACL + .getEntries + .getOrDefault(aclEntryKey(mailboxSession), 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)) + } + + private def retrieveIsSubscribed(path: MailboxPath, session: MailboxSession): IsSubscribed = IsSubscribed(subscriptionManager + .subscriptions(session) + .contains(path.getName)) + def create(mailboxMetaData: MailboxMetaData, mailboxSession: MailboxSession, allMailboxesMetadata: Seq[MailboxMetaData], - quotaLoader: QuotaLoader): SMono[Either[Exception, Mailbox]] = { + quotaLoader: QuotaLoader): SMono[Mailbox] = { val id: MailboxId = mailboxMetaData.getId val name: Either[IllegalArgumentException, MailboxName] = retrieveMailboxName(mailboxMetaData.getPath, mailboxSession) - 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 role: Option[Role] = getRole(mailboxMetaData.getPath, mailboxSession) + val sortOrder: SortOrder = getSortOrder(role) val quotas: SMono[Quotas] = quotaLoader.getQuotas(mailboxMetaData.getPath) - val rights: Rights = Rights.fromACL(MailboxACL.fromJava(mailboxMetaData.getResolvedAcls)) + val rights: Rights = getRights(mailboxMetaData.getResolvedAcls) val sanitizedCounters: MailboxCounters = mailboxMetaData.getCounters.sanitize() val unreadEmails: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getUnseen) @@ -90,54 +133,22 @@ class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager) { val totalEmails: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getCount) val totalThreads: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(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 namespace: MailboxNamespace = getNamespace(mailboxMetaData.getPath, mailboxSession) - val parentPath: Option[MailboxPath] = - mailboxMetaData.getPath - .getHierarchyLevels(mailboxSession.getPathDelimiter) - .asScala - .reverse - .drop(1) - .headOption + val parentPath: Option[MailboxPath] = getParentPath(mailboxMetaData.getPath, mailboxSession) 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)) - } + val myRights: MailboxRights = getMyRights(mailboxMetaData.getPath, mailboxMetaData.getResolvedAcls, mailboxSession) - def retrieveIsSubscribed: IsSubscribed = IsSubscribed(subscriptionManager - .subscriptions(mailboxSession) - .contains(mailboxMetaData.getPath.getName)) + val isSubscribed: IsSubscribed = retrieveIsSubscribed(mailboxMetaData.getPath, mailboxSession) MailboxValidation.validate(name, unreadEmails, unreadThreads, totalEmails, totalThreads) match { - case Left(error) => SMono.just(Left(error)) + case Left(error) => SMono.raiseError(error) case scala.Right(mailboxValidation) => SMono.fromPublisher(quotas) - .map(quotas => scala.Right( + .map(quotas => Mailbox( id = id, name = mailboxValidation.mailboxName, @@ -152,7 +163,60 @@ class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager) { namespace = namespace, rights = rights, quotas = quotas, - isSubscribed = retrieveIsSubscribed))) + isSubscribed = isSubscribed)) + } + } + + def create(id: MailboxId, mailboxSession: MailboxSession, quotaLoader: QuotaLoader): SMono[Mailbox] = { + try { + val messageManager: MessageManager = mailboxManager.getMailbox(id, mailboxSession) + val resolvedACL = messageManager.getResolvedAcl(mailboxSession) + + val name: Either[IllegalArgumentException, MailboxName] = retrieveMailboxName(messageManager.getMailboxPath, mailboxSession) + + val role: Option[Role] = getRole(messageManager.getMailboxPath, mailboxSession) + val sortOrder: SortOrder = getSortOrder(role) + val quotas: SMono[Quotas] = quotaLoader.getQuotas(messageManager.getMailboxPath) + val rights: Rights = getRights(resolvedACL) + + val sanitizedCounters: MailboxCounters = messageManager.getMailboxCounters(mailboxSession).sanitize() + val unreadEmails: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getUnseen) + val unreadThreads: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getUnseen) + val totalEmails: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getCount) + val totalThreads: Either[NumberFormatException, UnsignedInt] = UnsignedInt.validate(sanitizedCounters.getCount) + + val namespace: MailboxNamespace = getNamespace(messageManager.getMailboxPath, mailboxSession) + + val parentId: Option[MailboxId] = getParentPath(messageManager.getMailboxPath, mailboxSession) + .map(parentPath => mailboxManager.getMailbox(parentPath, mailboxSession)) + .map(_.getId) + + val myRights: MailboxRights = getMyRights(messageManager.getMailboxPath, resolvedACL, mailboxSession) + + val isSubscribed: IsSubscribed = retrieveIsSubscribed(messageManager.getMailboxPath, mailboxSession) + + MailboxValidation.validate(name, unreadEmails, unreadThreads, totalEmails, totalThreads) match { + case Left(error) => SMono.raiseError(error) + case scala.Right(mailboxValidation) => SMono.fromPublisher(quotas) + .map(quotas => + Mailbox( + id = id, + name = mailboxValidation.mailboxName, + parentId = parentId, + role = role, + sortOrder = sortOrder, + unreadEmails = mailboxValidation.unreadEmails, + totalEmails = mailboxValidation.totalEmails, + unreadThreads = mailboxValidation.unreadThreads, + totalThreads = mailboxValidation.totalThreads, + myRights = myRights, + namespace = namespace, + rights = rights, + quotas = quotas, + isSubscribed = isSubscribed)) + } + } catch { + case error: Exception => SMono.raiseError(error) } } } \ No newline at end of file diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala index 2449d6c..b27f06b 100644 --- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala +++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala @@ -156,7 +156,7 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers { accountId = ACCOUNT_ID, state = "75128aab4b1b", list = List(MAILBOX), - notFound = NotFound(List(MAILBOX_ID_1, MAILBOX_ID_2))) + notFound = NotFound(Set(MAILBOX_ID_1, MAILBOX_ID_2))) val expectedJson: String = """ --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
