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/05/13 02:03:29 UTC
[james-project] 08/09: JAMES-3091 JSON serialization for Mailbox
POJO
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 c01bf3a985407595fbfb2d50329718e47abf68c0
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Fri May 8 17:45:35 2020 +0700
JAMES-3091 JSON serialization for Mailbox POJO
---
server/protocols/jmap-rfc-8621/pom.xml | 5 +
.../org/apache/james/jmap/json/Serializer.scala | 73 ++++++++++-
.../scala/org/apache/james/jmap/mail/Mailbox.scala | 2 +-
.../scala/org/apache/james/jmap/mail/Quotas.scala | 19 ++-
.../james/jmap/json/MailboxSerializationTest.scala | 135 +++++++++++++++++++++
.../org/apache/james/jmap/mail/MailboxTest.scala | 12 +-
6 files changed, 233 insertions(+), 13 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index bb6a83b..9f2d220 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -78,6 +78,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>net.javacrumbs.json-unit</groupId>
+ <artifactId>json-unit-assertj</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
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 173a82d..87937a1 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
@@ -22,11 +22,14 @@ package org.apache.james.jmap.json
import java.io.InputStream
import java.net.URL
-import org.apache.james.core.Username
+import org.apache.james.core.{Domain, Username}
+import org.apache.james.jmap.mail.{DelegatedNamespace, 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.jmap.model
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
import org.apache.james.jmap.model.{Account, Invocation, Session, _}
+import org.apache.james.mailbox.Role
+import org.apache.james.mailbox.model.{MailboxACL, MailboxId}
import play.api.libs.functional.syntax._
import play.api.libs.json._
@@ -126,6 +129,70 @@ class Serializer {
private implicit val sessionWrites: Writes[Session] = Json.writes[Session]
+ private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
+ private implicit val roleWrites: Writes[Role] = role => JsString(role.serialize)
+ private implicit val sortOrderWrites: Writes[SortOrder] = Json.valueWrites[SortOrder]
+ private implicit val totalEmailsWrites: Writes[TotalEmails] = Json.valueWrites[TotalEmails]
+ private implicit val unreadEmailsWrites: Writes[UnreadEmails] = Json.valueWrites[UnreadEmails]
+ private implicit val totalThreadsWrites: Writes[TotalThreads] = Json.valueWrites[TotalThreads]
+ private implicit val unreadThreadsWrites: Writes[UnreadThreads] = Json.valueWrites[UnreadThreads]
+ private implicit val isSubscribedWrites: Writes[IsSubscribed] = Json.valueWrites[IsSubscribed]
+
+ private implicit val mayReadItemsWrites: Writes[MayReadItems] = Json.valueWrites[MayReadItems]
+ private implicit val mayAddItemsWrites: Writes[MayAddItems] = Json.valueWrites[MayAddItems]
+ private implicit val mayRemoveItemsWrites: Writes[MayRemoveItems] = Json.valueWrites[MayRemoveItems]
+ private implicit val maySetSeenWrites: Writes[MaySetSeen] = Json.valueWrites[MaySetSeen]
+ private implicit val maySetKeywordsWrites: Writes[MaySetKeywords] = Json.valueWrites[MaySetKeywords]
+ private implicit val mayCreateChildWrites: Writes[MayCreateChild] = Json.valueWrites[MayCreateChild]
+ private implicit val mayRenameWrites: Writes[MayRename] = Json.valueWrites[MayRename]
+ private implicit val mayDeleteWrites: Writes[MayDelete] = Json.valueWrites[MayDelete]
+ private implicit val maySubmitWrites: Writes[MaySubmit] = Json.valueWrites[MaySubmit]
+ private implicit val mailboxRightsWrites: Writes[MailboxRights] = Json.writes[MailboxRights]
+
+ private implicit val personalNamespaceWrites: Writes[PersonalNamespace] = namespace => JsString("Personal")
+ private implicit val delegatedNamespaceWrites: Writes[DelegatedNamespace] = namespace => JsString(s"Delegated[${namespace.owner.asString}]")
+ private implicit val mailboxNamespaceWrites: Writes[MailboxNamespace] = Json.writes[MailboxNamespace]
+
+ private implicit val mailboxACLWrites: Writes[MailboxACL.Right] = right => JsString(right.asCharacter.toString)
+
+ private implicit val rightWrites: Writes[Right] = Json.valueWrites[Right]
+ private implicit val rightsWrites: Writes[Rights] = Json.valueWrites[Rights]
+
+ private implicit def rightsMapWrites(implicit rightWriter: Writes[Seq[Right]]): Writes[Map[Username, Seq[Right]]] =
+ (m: Map[Username, Seq[Right]]) => {
+ m.foldLeft(JsObject.empty)((jsObject, kv) => {
+ val (username: Username, rights: Seq[Right]) = kv
+ jsObject.+(username.asString, rightWriter.writes(rights))
+ })
+ }
+
+ private implicit val domainWrites: Writes[Domain] = domain => JsString(domain.asString)
+ private implicit val quotaRootWrites: Writes[QuotaRoot] = Json.writes[QuotaRoot]
+ private implicit val quotaIdWrites: Writes[QuotaId] = Json.valueWrites[QuotaId]
+
+ private implicit val quotaValueWrites: Writes[Value] = Json.writes[Value]
+ private implicit val quotaWrites: Writes[Quota] = Json.valueWrites[Quota]
+
+ private implicit def quotaMapWrites(implicit valueWriter: Writes[Value]): Writes[Map[Quotas.Type, Value]] =
+ (m: Map[Quotas.Type, Value]) => {
+ m.foldLeft(JsObject.empty)((jsObject, kv) => {
+ val (quotaType: Quotas.Type, value: Value) = kv
+ jsObject.+(quotaType.toString, valueWriter.writes(value))
+ })
+ }
+
+ private implicit val quotasWrites: Writes[Quotas] = Json.valueWrites[Quotas]
+
+ private implicit def quotasMapWrites(implicit quotaWriter: Writes[Quota]): Writes[Map[QuotaId, Quota]] =
+ (m: Map[QuotaId, Quota]) => {
+ m.foldLeft(JsObject.empty)((jsObject, kv) => {
+ val (quotaId: QuotaId, quota: Quota) = kv
+ jsObject.+(quotaId.getName, quotaWriter.writes(quota))
+ })
+ }
+
+ private implicit val mailboxWrites: Writes[Mailbox] = Json.writes[Mailbox]
+
def serialize(session: Session): JsValue = {
Json.toJson(session)
}
@@ -138,6 +205,10 @@ class Serializer {
Json.toJson(responseObject)
}
+ def serialize(mailbox: Mailbox): JsValue = {
+ Json.toJson(mailbox)
+ }
+
def deserializeRequestObject(input: String): JsResult[RequestObject] = {
Json.parse(input).validate[RequestObject]
}
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 09f881f..aaff40e 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
@@ -56,7 +56,7 @@ object MailboxNamespace {
sealed trait MailboxNamespace
-case object PersonalNamespace extends MailboxNamespace
+case class PersonalNamespace() extends MailboxNamespace
case class DelegatedNamespace(owner: Username) extends MailboxNamespace
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
index be64e75..c9d4212 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
@@ -19,10 +19,19 @@
package org.apache.james.jmap.mail
-import java.util.Optional
-
+import org.apache.james.core.Domain
import org.apache.james.jmap.model.UnsignedInt.UnsignedInt
-import org.apache.james.mailbox.model.QuotaRoot
+import org.apache.james.mailbox.model.{QuotaRoot => JavaQuotaRoot}
+
+import scala.compat.java8.OptionConverters._
+
+object QuotaRoot{
+ def fromJava(quotaRoot: JavaQuotaRoot) = QuotaRoot(quotaRoot.getValue, quotaRoot.getDomain.asScala)
+}
+
+case class QuotaRoot(value: String, domain: Option[Domain]) {
+ def asJava: JavaQuotaRoot = JavaQuotaRoot.quotaRoot(value, domain.asJava)
+}
object Quotas {
sealed trait Type
@@ -39,11 +48,11 @@ object QuotaId {
}
case class QuotaId(quotaRoot: QuotaRoot) extends AnyVal {
- def getName: String = quotaRoot.getValue
+ def getName: String = quotaRoot.value
}
case class Quota(quota: Map[Quotas.Type, Value]) extends AnyVal
-case class Value(used: UnsignedInt, max: Optional[UnsignedInt])
+case class Value(used: UnsignedInt, max: Option[UnsignedInt])
case class Quotas(quotas: Map[QuotaId, Quota]) extends AnyVal
\ 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
new file mode 100644
index 0000000..5ab9690
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
@@ -0,0 +1,135 @@
+/****************************************************************
+ * 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.json
+
+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.{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}
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+import play.api.libs.json.Json
+
+object MailboxSerializationTest {
+ private val MAILBOX_ID: MailboxId = TestId.of(2)
+ private val MAILBOX_NAME: MailboxName = "inbox"
+ private val PARENT_ID: Option[MailboxId] = Option.apply(TestId.of(1))
+ private val ROLE: Option[Role] = Option.apply(Role.INBOX)
+ private val SORT_ORDER: SortOrder = SortOrder(10L)
+ private val TOTAL_EMAILS: TotalEmails = TotalEmails(1234L)
+ private val UNREAD_EMAILS: UnreadEmails = UnreadEmails(123L)
+ private val TOTAL_THREADS: TotalThreads = TotalThreads(58L)
+ private val UNREAD_THREADS: UnreadThreads = UnreadThreads(22L)
+ private val IS_SUBSCRIBED: IsSubscribed = IsSubscribed(true)
+ private val NAMESPACE: MailboxNamespace = PersonalNamespace()
+
+ private val MY_RIGHTS: MailboxRights = MailboxRights(
+ mayAddItems = MayAddItems(true),
+ mayReadItems = MayReadItems(false),
+ mayRemoveItems = MayRemoveItems(false),
+ maySetSeen = MaySetSeen(true),
+ maySetKeywords = MaySetKeywords(false),
+ mayCreateChild = MayCreateChild(true),
+ mayRename = MayRename(true),
+ mayDelete = MayDelete(false),
+ maySubmit = MaySubmit(false)
+ )
+
+ private val RIGHTS: Rights = Rights.of(Username.of("bob"), Seq(Right.Expunge, Right.Lookup))
+ .append(Username.of("alice"), Seq(Right.Read, Right.Write))
+
+ private val QUOTAS = Quotas(Map(
+ QuotaId(QuotaRoot("quotaRoot", None)) -> Quota(Map(
+ Quotas.Message -> Value(18L, Some(42L)),
+ Quotas.Storage -> Value(12L, None))),
+ QuotaId(QuotaRoot("quotaRoot2@localhost", Some(Domain.LOCALHOST))) -> Quota(Map(
+ Quotas.Message -> Value(14L, Some(43L)),
+ Quotas.Storage -> Value(19L, None)))))
+
+ private val MAILBOX: Mailbox = Mailbox(
+ id = MAILBOX_ID,
+ name = MAILBOX_NAME,
+ parentId = PARENT_ID,
+ role = ROLE,
+ sortOrder = SORT_ORDER,
+ totalEmails = TOTAL_EMAILS,
+ unreadEmails = UNREAD_EMAILS,
+ totalThreads = TOTAL_THREADS,
+ unreadThreads = UNREAD_THREADS,
+ myRights = MY_RIGHTS,
+ isSubscribed = IS_SUBSCRIBED,
+ namespace = NAMESPACE,
+ rights = RIGHTS,
+ quotas = QUOTAS
+ )
+}
+
+class MailboxSerializationTest extends AnyWordSpec with Matchers {
+ "Serialize Mailbox" should {
+ "succeed " in {
+
+ val expectedJson = Json.parse(
+ """{
+ | "id":"2",
+ | "name":"inbox",
+ | "parentId":"1",
+ | "role":"inbox",
+ | "sortOrder":10,
+ | "totalEmails":1234,
+ | "unreadEmails":123,
+ | "totalThreads":58,
+ | "unreadThreads":22,
+ | "myRights":{
+ | "mayReadItems":false,
+ | "mayAddItems":true,
+ | "mayRemoveItems":false,
+ | "maySetSeen":true,
+ | "maySetKeywords":false,
+ | "mayCreateChild":true,
+ | "mayRename":true,
+ | "mayDelete":false,
+ | "maySubmit":false
+ | },
+ | "isSubscribed":true,
+ | "namespace":"Personal",
+ | "rights":{
+ | "bob":["e","l"],
+ | "alice":["r","w"]
+ | },
+ | "quotas":{
+ | "quotaRoot":{
+ | "Message":{"used":18,"max":42},
+ | "Storage":{"used":12}
+ | },
+ | "quotaRoot2@localhost":{
+ | "Message":{"used":14,"max":43},
+ | "Storage":{"used":19}
+ | }
+ | }
+ |}""".stripMargin)
+
+ assertThatJson(new Serializer().serialize(MAILBOX)).isEqualTo(expectedJson)
+ }
+ }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/mail/MailboxTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/mail/MailboxTest.scala
index 018c2bb..47e8c95 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/mail/MailboxTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/mail/MailboxTest.scala
@@ -58,7 +58,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map()))
.hasRole(Role.INBOX) must be(false)
@@ -84,7 +84,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map()))
.hasRole(Role.INBOX) must be(false)
@@ -110,7 +110,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map())).hasRole(Role.INBOX) must be(true)
}
@@ -138,7 +138,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map()))
.hasSystemRole must be(false)
@@ -164,7 +164,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map())).hasSystemRole must be(false)
}
@@ -189,7 +189,7 @@ class MailboxTest extends AnyWordSpec with Matchers {
MayDelete(true),
MaySubmit(true)),
IsSubscribed(true),
- MailboxNamespace.personal,
+ MailboxNamespace.personal(),
Rights.EMPTY,
Quotas(Map())).hasSystemRole must be(true)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org