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 bt...@apache.org on 2018/12/13 02:48:27 UTC

[1/6] james-project git commit: Move changelog entries to the 'unreleased' section

Repository: james-project
Updated Branches:
  refs/heads/master e6de4b4e9 -> eb5874dd4


Move changelog entries to the 'unreleased' section


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/eba7e5b6
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/eba7e5b6
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/eba7e5b6

Branch: refs/heads/master
Commit: eba7e5b66e82ff2e95fab6e287bfbb14bc8b9e47
Parents: 6e589fc
Author: Benoit Tellier <bt...@linagora.com>
Authored: Thu Dec 6 10:31:49 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:46:49 2018 +0700

----------------------------------------------------------------------
 CHANGELOG.md | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/eba7e5b6/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68fe832..cfe0fe5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,22 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 
 ## [Unreleased]
- 
-## [3.2.0] - 2018-11-XX
+
+### Added
+- Metrics for BlobStore
+- New Guice product using Cassandra RabbitMQ ElasticSearch, OpenStack Swift and optional LDAP dependency (experimental)
+
+### Fixed
+- MAILBOX-350 Potential invalid UID <-> MSN mapping upon IMAP COPY
+- Possibility to better zoom in Grafana boards
+
+### Changed
+- WebAdmin ReIndexing API had been reworked
+
+### Removed
+- Drop HBase and JCR components (mailbox and server/data).
+
+## [3.2.0] - 2018-11-14
 ### Added
 - Mail filtering configured via the JMAP protocol
 - WebAdmin exposed mail re-indexing tasks
@@ -18,18 +32,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 - Mailet DOC: Exclude from documentation annotation thanks to [mschnitzler](https://github.com/mschnitzler)
 - `cassandra.pooling.max.queue.size` configuration option Thanks to [matzepan](https://github.com/matzepan)
 - `RecipentDomainIs` and `SenderDomainIs` matchers by [athulyaraj](https://github.com/athulyaraj)
-- Metrics for BlobStore
-- New Guice product using Cassandra RabbitMQ ElasticSearch, OpenStack Swift and optional LDAP dependency (experiemental)
-
-### Fixed
-- MAILBOX-350 Potential invalid UID <-> MSN mapping upon IMAP COPY
-- Possibility to better zoom in Grafana boards
 
 ### Changed
 - Multiple libraries updates
 - Migration from Cassandra 2 to Cassandra 3
 - Mail::getSender was deprecated. Mail::getMaybeSender offers better Null Sender support. Java 8 default API method was used to not break compatibility.
-- WebAdmin ReIndexing API had been reworked
 
 ### Deprecated
  - HBase and JCR components (mailbox and server/data). This will be removed as part of 3.3.0. If you have development skills, and are willing to maintain these components, please reach us.


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


[6/6] james-project git commit: MAILBOX-360 Scala QuotaUpdateEvent and tests

Posted by bt...@apache.org.
MAILBOX-360 Scala QuotaUpdateEvent and tests


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/eb5874dd
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/eb5874dd
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/eb5874dd

Branch: refs/heads/master
Commit: eb5874dd49526cd23287f75cf995cdc458e9652d
Parents: 07b68d3
Author: tran tien duc <dt...@linagora.com>
Authored: Wed Dec 12 17:01:44 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:47:40 2018 +0700

----------------------------------------------------------------------
 mailbox/event/json/pom.xml                      | 155 ++++
 .../apache/james/event/json/QuotaEvent.scala    | 135 ++++
 .../apache/james/event/json/QuotaEventTest.java | 760 +++++++++++++++++++
 mailbox/pom.xml                                 |   1 +
 4 files changed, 1051 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/eb5874dd/mailbox/event/json/pom.xml
----------------------------------------------------------------------
diff --git a/mailbox/event/json/pom.xml b/mailbox/event/json/pom.xml
new file mode 100644
index 0000000..bc2621d
--- /dev/null
+++ b/mailbox/event/json/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>apache-james-mailbox</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.3.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>apache-james-mailbox-event-json</artifactId>
+    <name>Apache James :: Mailbox :: Event :: JSON</name>
+    <description>Apache James Mailbox Event JSON Scala Serialization</description>
+
+    <properties>
+        <scala.base>2.12</scala.base>
+        <scala.version>${scala.base}.7</scala.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.beachape</groupId>
+            <artifactId>enumeratum_${scala.base}</artifactId>
+            <version>1.5.13</version>
+        </dependency>
+        <dependency>
+            <groupId>com.typesafe.play</groupId>
+            <artifactId>play-json_${scala.base}</artifactId>
+            <version>2.6.10</version>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.julienrf</groupId>
+            <artifactId>play-json-derived-codecs_${scala.base}</artifactId>
+            <version>4.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.scala-lang</groupId>
+            <artifactId>scala-library</artifactId>
+            <version>${scala.version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.scala-lang.modules</groupId>
+          <artifactId>scala-java8-compat_${scala.base}</artifactId>
+          <version>0.9.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>net.alchim31.maven</groupId>
+                    <artifactId>scala-maven-plugin</artifactId>
+                    <version>3.4.4</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>2.0.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <reuseForks>true</reuseForks>
+                    <forkCount>1C</forkCount>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>net.alchim31.maven</groupId>
+                <artifactId>scala-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>scala-compile-first</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>add-source</goal>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>scala-test-compile</id>
+                        <phase>process-test-resources</phase>
+                        <goals>
+                            <goal>testCompile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <addArgs>-Xlog-implicits</addArgs>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/eb5874dd/mailbox/event/json/src/main/scala/org/apache/james/event/json/QuotaEvent.scala
----------------------------------------------------------------------
diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/QuotaEvent.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/QuotaEvent.scala
new file mode 100644
index 0000000..26be6d6
--- /dev/null
+++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/QuotaEvent.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.event.json
+
+import java.time.Instant
+import java.util.Optional
+
+import julienrf.json.derived
+import org.apache.james.core.quota.{QuotaCount, QuotaSize, QuotaValue}
+import org.apache.james.core.{Domain, User}
+import org.apache.james.mailbox.MailboxListener.{QuotaEvent => JavaQuotaEvent, QuotaUsageUpdatedEvent => JavaQuotaUsageUpdatedEvent}
+import org.apache.james.mailbox.model.{QuotaRoot, Quota => JavaQuota}
+import play.api.libs.json.{JsError, JsNull, JsNumber, JsObject, JsResult, JsString, JsSuccess, Json, OFormat, Reads, Writes}
+
+import scala.collection.JavaConverters._
+import scala.compat.java8.OptionConverters
+
+private sealed trait QuotaEvent {
+  def getQuotaRoot: QuotaRoot
+
+  def toJava: JavaQuotaEvent
+}
+
+private object DTO {
+
+  case class Quota[T <: QuotaValue[T]](used: T, limit: T, limits: Map[JavaQuota.Scope, T]) {
+    def toJava: JavaQuota[T] =
+      JavaQuota.builder[T]
+        .used(used)
+        .computedLimit(limit)
+        .limitsByScope(limits.asJava)
+        .build()
+  }
+
+  case class QuotaUsageUpdatedEvent(user: User, quotaRoot: QuotaRoot, countQuota: Quota[QuotaCount],
+                                    sizeQuota: Quota[QuotaSize], time: Instant) extends QuotaEvent {
+    override def getQuotaRoot: QuotaRoot = quotaRoot
+
+    override def toJava: JavaQuotaEvent =
+      new JavaQuotaUsageUpdatedEvent(user, getQuotaRoot, countQuota.toJava, sizeQuota.toJava, time)
+  }
+
+}
+
+private object JsonSerialize {
+  implicit val userWriters: Writes[User] = (user: User) => JsString(user.asString)
+  implicit val quotaRootWrites: Writes[QuotaRoot] = quotaRoot => JsString(quotaRoot.getValue)
+  implicit val quotaValueWrites: Writes[QuotaValue[_]] = value => if (value.isUnlimited) JsNull else JsNumber(value.asLong())
+  implicit val quotaScopeWrites: Writes[JavaQuota.Scope] = value => JsString(value.name)
+  implicit val quotaCountWrites: Writes[DTO.Quota[QuotaCount]] = Json.writes[DTO.Quota[QuotaCount]]
+  implicit val quotaSizeWrites: Writes[DTO.Quota[QuotaSize]] = Json.writes[DTO.Quota[QuotaSize]]
+
+  implicit val userReads: Reads[User] = {
+    case JsString(userAsString) => JsSuccess(User.fromUsername(userAsString))
+    case _ => JsError()
+  }
+  implicit val quotaRootReads: Reads[QuotaRoot] = {
+    case JsString(quotaRoot) => JsSuccess(QuotaRoot.quotaRoot(quotaRoot, Optional.empty[Domain]))
+    case _ => JsError()
+  }
+  implicit val quotaCountReads: Reads[QuotaCount] = {
+    case JsNumber(count) => JsSuccess(QuotaCount.count(count.toLong))
+    case JsNull => JsSuccess(QuotaCount.unlimited())
+    case _ => JsError()
+  }
+  implicit val quotaSizeReads: Reads[QuotaSize] = {
+    case JsNumber(size) => JsSuccess(QuotaSize.size(size.toLong))
+    case JsNull => JsSuccess(QuotaSize.unlimited())
+    case _ => JsError()
+  }
+  implicit val quotaScopeReads: Reads[JavaQuota.Scope] = {
+    case JsString(value) => JsSuccess(JavaQuota.Scope.valueOf(value))
+    case _ => JsError()
+  }
+
+  implicit def scopeMapReads[V](implicit vr: Reads[V]): Reads[Map[JavaQuota.Scope, V]] =
+    Reads.mapReads[JavaQuota.Scope, V] { str =>
+      Json.fromJson[JavaQuota.Scope](JsString(str))
+    }
+
+  implicit def scopeMapWrite[V](implicit vr: Writes[V]): Writes[Map[JavaQuota.Scope, V]] =
+    (m: Map[JavaQuota.Scope, V]) => {
+      JsObject(m.map { case (k, v) => (k.toString, vr.writes(v)) }.toSeq)
+    }
+
+  implicit val quotaCReads: Reads[DTO.Quota[QuotaCount]] = Json.reads[DTO.Quota[QuotaCount]]
+  implicit val quotaSReads: Reads[DTO.Quota[QuotaSize]] = Json.reads[DTO.Quota[QuotaSize]]
+
+  implicit val quotaEventOFormat: OFormat[QuotaEvent] = derived.oformat()
+
+  def toJson(event: QuotaEvent): String = Json.toJson(event).toString()
+
+  def fromJson(json: String): JsResult[QuotaEvent] = Json.fromJson[QuotaEvent](Json.parse(json))
+}
+
+object QuotaEvent {
+
+  private def toScala[T <: QuotaValue[T]](java: JavaQuota[T]): DTO.Quota[T] =
+    DTO.Quota(used = java.getUsed, limit = java.getLimit, limits = java.getLimitByScope.asScala.toMap)
+
+  private def toScala(event: JavaQuotaUsageUpdatedEvent): DTO.QuotaUsageUpdatedEvent =
+    DTO.QuotaUsageUpdatedEvent(
+      user = event.getUser,
+      quotaRoot = event.getQuotaRoot,
+      countQuota = toScala(event.getCountQuota),
+      sizeQuota = toScala(event.getSizeQuota),
+      time = event.getInstant)
+
+  def toJson(event: JavaQuotaEvent): String = event match {
+    case e: JavaQuotaUsageUpdatedEvent => JsonSerialize.toJson(toScala(e))
+    case _ => throw new RuntimeException("no encoder found")
+  }
+
+  def fromJson(json: String): JsResult[JavaQuotaEvent] = {
+    JsonSerialize.fromJson(json)
+      .map(event => event.toJava)
+  }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/eb5874dd/mailbox/event/json/src/test/java/org/apache/james/event/json/QuotaEventTest.java
----------------------------------------------------------------------
diff --git a/mailbox/event/json/src/test/java/org/apache/james/event/json/QuotaEventTest.java b/mailbox/event/json/src/test/java/org/apache/james/event/json/QuotaEventTest.java
new file mode 100644
index 0000000..9dc3a04
--- /dev/null
+++ b/mailbox/event/json/src/test/java/org/apache/james/event/json/QuotaEventTest.java
@@ -0,0 +1,760 @@
+/****************************************************************
+ * 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.event.json;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.Instant;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import org.apache.james.core.User;
+import org.apache.james.core.quota.QuotaCount;
+import org.apache.james.core.quota.QuotaSize;
+import org.apache.james.mailbox.MailboxListener;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class QuotaEventTest {
+
+    private static final User USER = User.fromUsername("user");
+    private static final QuotaRoot QUOTA_ROOT = QuotaRoot.quotaRoot("foo", Optional.empty());
+    private static final Quota<QuotaCount> QUOTA_COUNT = Quota.<QuotaCount>builder()
+        .used(QuotaCount.count(12))
+        .computedLimit(QuotaCount.count(100))
+        .build();
+    private static final Quota<QuotaSize> QUOTA_SIZE = Quota.<QuotaSize>builder()
+        .used(QuotaSize.size(1234))
+        .computedLimit(QuotaSize.size(10000))
+        .build();
+    private static final Instant INSTANT = Instant.parse("2018-11-13T12:00:55Z");
+    private static final MailboxListener.QuotaUsageUpdatedEvent DEFAULT_QUOTA_EVENT =
+        new MailboxListener.QuotaUsageUpdatedEvent(USER, QUOTA_ROOT, QUOTA_COUNT, QUOTA_SIZE, INSTANT);
+
+    private static final String DEFAULT_QUOTA_EVENT_JSON =
+        "{" +
+            "\"QuotaUsageUpdatedEvent\":{" +
+            "\"quotaRoot\":\"foo\"," +
+            "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+            "\"time\":\"2018-11-13T12:00:55Z\"," +
+            "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+            "\"user\":\"user\"" +
+            "}" +
+        "}";
+
+    private static final QuotaEvent$ QUOTA_EVENT_MODULE = QuotaEvent$.MODULE$;
+
+    @Nested
+    class WithUser {
+
+        @Nested
+        class WithValidUser {
+
+            @Nested
+            class WithUserContainsOnlyUsername {
+
+                private final MailboxListener.QuotaUsageUpdatedEvent eventWithUserContainsUsername = new MailboxListener.QuotaUsageUpdatedEvent(
+                    User.fromUsername("onlyUsername"),
+                    QUOTA_ROOT,
+                    QUOTA_COUNT,
+                    QUOTA_SIZE,
+                    INSTANT);
+                private final String quotaUsageUpdatedEvent =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"onlyUsername\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void fromJsonShouldReturnQuotaEvent() {
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                        .isEqualTo(eventWithUserContainsUsername);
+                }
+
+                @Test
+                void toJsonShouldReturnQuotaEventJson() {
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(eventWithUserContainsUsername))
+                        .isEqualTo(quotaUsageUpdatedEvent);
+                }
+            }
+
+            @Nested
+            class WithUserContainsUsernameAndDomain {
+
+                private final MailboxListener.QuotaUsageUpdatedEvent eventWithUserContainsUsernameAndDomain = new MailboxListener.QuotaUsageUpdatedEvent(
+                    User.fromUsername("user@domain"),
+                    QUOTA_ROOT,
+                    QUOTA_COUNT,
+                    QUOTA_SIZE,
+                    INSTANT);
+                private final String quotaUsageUpdatedEvent =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user@domain\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void fromJsonShouldReturnQuotaEvent() {
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                        .isEqualTo(eventWithUserContainsUsernameAndDomain);
+                }
+
+                @Test
+                void toJsonShouldReturnQuotaEventJson() {
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(eventWithUserContainsUsernameAndDomain))
+                        .isEqualTo(quotaUsageUpdatedEvent);
+                }
+            }
+        }
+
+        @Nested
+        class WithInvalidUser {
+
+            @Test
+            void fromJsonShouldThrowWhenEmptyUser() {
+                String quotaUsageUpdatedEvent =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"\"" +
+                        "}" +
+                    "}";
+                assertThatThrownBy(() -> QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent))
+                    .isInstanceOf(IllegalArgumentException.class);
+            }
+
+
+            @Test
+            void fromJsonShouldThrowResultWhenUserIsNull() {
+                String quotaUsageUpdatedEvent =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}" +
+                        "}" +
+                    "}";
+
+                assertThatThrownBy(() ->QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                    .isInstanceOf(NoSuchElementException.class);
+            }
+
+            @Test
+            void fromJsonShouldThrowWhenUserIsInvalid() {
+                String quotaUsageUpdatedEvent =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"@domain\"" +
+                        "}" +
+                    "}";
+                assertThatThrownBy(() -> QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent))
+                    .isInstanceOf(IllegalArgumentException.class);
+            }
+        }
+
+    }
+
+    @Nested
+    class WitQuotaRoot {
+
+        @Nested
+        class WithNormalQuotaRoot {
+
+            @Test
+            void toJsonShouldReturnSerializedJsonQuotaRoot() {
+                assertThatJson(QUOTA_EVENT_MODULE.toJson(DEFAULT_QUOTA_EVENT))
+                    .isEqualTo(DEFAULT_QUOTA_EVENT_JSON);
+            }
+
+            @Test
+            void fromJsonShouldDeserializeQuotaRootJson() {
+                assertThat(QUOTA_EVENT_MODULE.fromJson(DEFAULT_QUOTA_EVENT_JSON).get())
+                    .isEqualTo(DEFAULT_QUOTA_EVENT);
+            }
+        }
+
+        @Nested
+        class WithEmptyQuotaRoot {
+            private final QuotaRoot emptyQuotaRoot = QuotaRoot.quotaRoot("", Optional.empty());
+            private final MailboxListener.QuotaUsageUpdatedEvent eventWithEmptyQuotaRoot =
+                new MailboxListener.QuotaUsageUpdatedEvent(
+                    USER,
+                    emptyQuotaRoot,
+                    QUOTA_COUNT,
+                    QUOTA_SIZE,
+                    INSTANT);
+            private final String quotaUsageUpdatedEvent =
+                "{" +
+                    "\"QuotaUsageUpdatedEvent\":{" +
+                    "\"quotaRoot\":\"\"," +
+                    "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                    "\"time\":\"2018-11-13T12:00:55Z\"," +
+                    "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                    "\"user\":\"user\"" +
+                    "}" +
+                "}";
+
+            @Test
+            void toJsonShouldSerializeWithEmptyQuotaRoot() {
+                assertThatJson(QUOTA_EVENT_MODULE.toJson(eventWithEmptyQuotaRoot))
+                    .isEqualTo(quotaUsageUpdatedEvent);
+            }
+
+            @Test
+            void fromJsonShouldDeserializeWithEmptyQuotaRoot() {
+                assertThat(QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                    .isEqualTo(eventWithEmptyQuotaRoot);
+            }
+        }
+
+        @Nested
+        class WithNullQuotaRoot {
+            private final MailboxListener.QuotaUsageUpdatedEvent eventWithNullQuotaRoot =
+                new MailboxListener.QuotaUsageUpdatedEvent(
+                    USER,
+                    null,
+                    QUOTA_COUNT,
+                    QUOTA_SIZE,
+                    INSTANT);
+
+            private final String quotaUsageUpdatedEvent =
+                "{" +
+                    "\"QuotaUsageUpdatedEvent\":{" +
+                    "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                    "\"time\":\"2018-11-13T12:00:55Z\"," +
+                    "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                    "\"user\":\"user\"" +
+                    "}" +
+                "}";
+
+            @Test
+            void toJsonShouldThrowWithNullQuotaRoot() {
+                assertThatThrownBy(() -> QUOTA_EVENT_MODULE.toJson(eventWithNullQuotaRoot))
+                    .isInstanceOf(NullPointerException.class);
+            }
+
+            @Test
+            void fromJsonShouldThrowWithNullQuotaRoot() {
+                assertThatThrownBy(() -> QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                    .isInstanceOf(NoSuchElementException.class);
+            }
+        }
+    }
+
+    
+    @Nested
+    class WithQuotaCount {
+
+        private MailboxListener.QuotaUsageUpdatedEvent quotaEventByQuotaCount(Quota<QuotaCount> countQuota) {
+            return new MailboxListener.QuotaUsageUpdatedEvent(USER, QUOTA_ROOT, countQuota, QUOTA_SIZE, INSTANT);
+        }
+
+        @Nested
+        class LimitedQuotaCount {
+
+            private Quota<QuotaCount> limitedQuotaCountByScopes(Quota.Scope scope) {
+                return Quota.<QuotaCount>builder()
+                    .used(QuotaCount.count(12))
+                    .computedLimit(QuotaCount.count(100))
+                    .limitForScope(QuotaCount.count(100), scope)
+                    .build();
+            }
+
+            @Nested
+            class LimitedQuotaGlobalScope {
+
+                private final String limitedQuotaCountEventJsonGlobalScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{\"Global\":100}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.Global));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaCountEventJsonGlobalScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.Global));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaCountEventJsonGlobalScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class LimitedQuotaDomainScope {
+                private final String limitedQuotaCountEventJsonDomainScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{\"Domain\":100}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.Domain));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaCountEventJsonDomainScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.Domain));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaCountEventJsonDomainScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class LimitedQuotaUserScope {
+                private final String limitedQuotaCountEventJsonUserScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{\"User\":100}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.User));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaCountEventJsonUserScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(limitedQuotaCountByScopes(Quota.Scope.User));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaCountEventJsonUserScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+        }
+
+        @Nested
+        class UnLimitedQuotaCount {
+
+            private Quota<QuotaCount> unLimitedQuotaCountByScopes(Quota.Scope scope) {
+                return Quota.<QuotaCount>builder()
+                    .used(QuotaCount.count(12))
+                    .computedLimit(QuotaCount.unlimited())
+                    .limitForScope(QuotaCount.unlimited(), scope)
+                    .build();
+            }
+
+            @Nested
+            class UnLimitedQuotaGlobalScope {
+                private final String unLimitedQuotaCountEventJsonGlobalScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":null,\"limits\":{\"Global\":null}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.Global));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaCountEventJsonGlobalScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.Global));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaCountEventJsonGlobalScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class UnLimitedQuotaDomainScope {
+                private final String unLimitedQuotaCountEventJsonDomainScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":null,\"limits\":{\"Domain\":null}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.Domain));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaCountEventJsonDomainScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.Domain));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaCountEventJsonDomainScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class UnLimitedQuotaUserScope {
+                private final String unLimitedQuotaCountEventJsonUserScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":null,\"limits\":{\"User\":null}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.User));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaCountEventJsonUserScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaCount() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaCount(unLimitedQuotaCountByScopes(Quota.Scope.User));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaCountEventJsonUserScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+        }
+    }
+
+    
+    @Nested
+    class WithQuotaSize {
+
+        private MailboxListener.QuotaUsageUpdatedEvent quotaEventByQuotaSize(Quota<QuotaSize> quotaSize) {
+            return new MailboxListener.QuotaUsageUpdatedEvent(USER, QUOTA_ROOT, QUOTA_COUNT, quotaSize, INSTANT);
+        }
+
+        @Nested
+        class LimitedQuotaSize {
+
+            private Quota<QuotaSize> limitedQuotaSizeByScopes(Quota.Scope scope) {
+                return Quota.<QuotaSize>builder()
+                    .used(QuotaSize.size(1234))
+                    .computedLimit(QuotaSize.size(10000))
+                    .limitForScope(QuotaSize.size(10000), scope)
+                    .build();
+            }
+
+            @Nested
+            class LimitedQuotaSizeGlobalScope {
+
+                private final String limitedQuotaSizeEventJsonGlobalScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{\"Global\":10000}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.Global));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaSizeEventJsonGlobalScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.Global));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaSizeEventJsonGlobalScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class LimitedQuotaSizeDomainScope {
+                private final String limitedQuotaSizeEventJsonDomainScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{\"Domain\":10000}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.Domain));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaSizeEventJsonDomainScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.Domain));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaSizeEventJsonDomainScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class LimitedQuotaSizeUserScope {
+                private final String limitedQuotaSizeEventJsonUserScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{\"User\":10000}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.User));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(limitedQuotaSizeEventJsonUserScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(limitedQuotaSizeByScopes(Quota.Scope.User));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(limitedQuotaSizeEventJsonUserScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+        }
+
+        @Nested
+        class UnLimitedQuotaSize {
+
+            private Quota<QuotaSize> unLimitedQuotaSizeByScopes(Quota.Scope scope) {
+                return Quota.<QuotaSize>builder()
+                    .used(QuotaSize.size(1234))
+                    .computedLimit(QuotaSize.unlimited())
+                    .limitForScope(QuotaSize.unlimited(), scope)
+                    .build();
+            }
+
+            @Nested
+            class UnLimitedQuotaSizeGlobalScope {
+
+                private final String unLimitedQuotaSizeEventJsonGlobalScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":null,\"limits\":{\"Global\":null}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.Global));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaSizeEventJsonGlobalScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.Global));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaSizeEventJsonGlobalScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class UnLimitedQuotaSizeDomainScope {
+                private final String unLimitedQuotaSizeEventJsonDomainScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":null,\"limits\":{\"Domain\":null}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.Domain));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaSizeEventJsonDomainScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.Domain));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaSizeEventJsonDomainScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+
+            @Nested
+            class UnLimitedQuotaSizeUserScope {
+                private final String unLimitedQuotaSizeEventJsonUserScope =
+                    "{" +
+                        "\"QuotaUsageUpdatedEvent\":{" +
+                        "\"quotaRoot\":\"foo\"," +
+                        "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{}}," +
+                        "\"time\":\"2018-11-13T12:00:55Z\"," +
+                        "\"sizeQuota\":{\"used\":1234,\"limit\":null,\"limits\":{\"User\":null}}," +
+                        "\"user\":\"user\"" +
+                        "}" +
+                    "}";
+
+                @Test
+                void toJsonShouldSerializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.User));
+
+                    assertThatJson(QUOTA_EVENT_MODULE.toJson(quotaEvent))
+                        .isEqualTo(unLimitedQuotaSizeEventJsonUserScope);
+                }
+
+                @Test
+                void fromJsonShouldDeserializeQuotaSize() {
+                    MailboxListener.QuotaUsageUpdatedEvent quotaEvent = quotaEventByQuotaSize(unLimitedQuotaSizeByScopes(Quota.Scope.User));
+
+                    assertThat(QUOTA_EVENT_MODULE.fromJson(unLimitedQuotaSizeEventJsonUserScope).get())
+                        .isEqualTo(quotaEvent);
+                }
+            }
+        }
+    }
+
+    @Nested
+    class WithTime {
+
+        @Test
+        void toJsonShouldReturnSerializedJsonEventWhenTimeIsValid() {
+            assertThatJson(QUOTA_EVENT_MODULE.toJson(DEFAULT_QUOTA_EVENT))
+                .isEqualTo(DEFAULT_QUOTA_EVENT_JSON);
+        }
+
+        @Test
+        void fromJsonShouldReturnDeSerializedEventWhenTimeIsValid() {
+            assertThat(QUOTA_EVENT_MODULE.fromJson(DEFAULT_QUOTA_EVENT_JSON).get())
+                .isEqualTo(DEFAULT_QUOTA_EVENT);
+        }
+
+        @Test
+        void fromJsonShouldThrowResultWhenTimeIsNull() {
+            String quotaUsageUpdatedEvent =
+                "{" +
+                    "\"QuotaUsageUpdatedEvent\":{" +
+                    "\"quotaRoot\":\"foo\"," +
+                    "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{\"Domain\":100}}," +
+                    "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                    "\"user\":\"user\"" +
+                    "}" +
+                "}";
+
+            assertThatThrownBy(() -> QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                .isInstanceOf(NoSuchElementException.class);
+        }
+
+        @Test
+        void fromJsonShouldThrowResultWhenTimeIsEmpty() {
+            String quotaUsageUpdatedEvent =
+                "{" +
+                    "\"QuotaUsageUpdatedEvent\":{" +
+                    "\"quotaRoot\":\"foo\"," +
+                    "\"countQuota\":{\"used\":12,\"limit\":100,\"limits\":{\"Domain\":100}}," +
+                    "\"time\":\"\"," +
+                    "\"sizeQuota\":{\"used\":1234,\"limit\":10000,\"limits\":{}}," +
+                    "\"user\":\"user\"" +
+                    "}" +
+                "}";
+
+            assertThatThrownBy(() -> QUOTA_EVENT_MODULE.fromJson(quotaUsageUpdatedEvent).get())
+                .isInstanceOf(NoSuchElementException.class);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/eb5874dd/mailbox/pom.xml
----------------------------------------------------------------------
diff --git a/mailbox/pom.xml b/mailbox/pom.xml
index ff409ce..60cbe44 100644
--- a/mailbox/pom.xml
+++ b/mailbox/pom.xml
@@ -40,6 +40,7 @@
         <module>caching</module>
         <module>cassandra</module>
         <module>elasticsearch</module>
+        <module>event/json</module>
         <module>jpa</module>
         <module>lucene</module>
         <module>maildir</module>


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


[4/6] james-project git commit: Document MailboxListener related changes

Posted by bt...@apache.org.
Document MailboxListener related changes


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/c03515b0
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/c03515b0
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/c03515b0

Branch: refs/heads/master
Commit: c03515b0ae9f6a426ca355cbb8f0f80e7270bf84
Parents: eba7e5b
Author: Benoit Tellier <bt...@linagora.com>
Authored: Mon Dec 10 11:14:48 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:46:50 2018 +0700

----------------------------------------------------------------------
 CHANGELOG.md            |  1 +
 upgrade-instructions.md | 27 +++++++++++++++++++++++++++
 2 files changed, 28 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/c03515b0/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfe0fe5..67725c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 
 ### Changed
 - WebAdmin ReIndexing API had been reworked
+- MailboxListener and mailbox event system was reworked. Custom listeners will need to be adapted. Please see Upgrade instuctions.
 
 ### Removed
 - Drop HBase and JCR components (mailbox and server/data).

http://git-wip-us.apache.org/repos/asf/james-project/blob/c03515b0/upgrade-instructions.md
----------------------------------------------------------------------
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index 0a1fd3c..38c6fc2 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -18,6 +18,33 @@ Change list:
 
  - [Changes in WebAdmin reIndexing API](#changes-in-webadmin-reindexing-api)
 
+### Changes to the MailboxListener API
+
+#### Persistent MailboxId for MailDir
+
+Date: 30/11/2018
+
+SHA-1: 7e32da51a29bee1c732b2b13708bb4b986140119
+
+JIRA: https://issues.apache.org/jira/browse/MAILBOX-292
+
+MailboxId are now persisted in a `james-mailboxId` file. This file is created on the fly, so no action is required for users relying on
+the MailDir mailbox.
+
+#### Registration by MailboxId
+
+Date: 30/11/2018
+
+SHA-1: d9bcebc7dd546bd5f11f3d9b496491e7c9042fe2
+
+JIRA: https://issues.apache.org/jira/browse/MAILBOX-354
+
+Only user written components performing MailboxListener registration will be affected.
+
+The MailboxPath is mutable and thus can be changed upon mailbox rename. This leads to significantly complex code with possible inconsistency windows.
+
+Using the mailboxId, which is immutable, solves these issues.
+
 ### Changes in WebAdmin reIndexing API
 
 Date: 05/12/2018


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


[5/6] james-project git commit: Add missing previous Sprint entries to the Changelog

Posted by bt...@apache.org.
Add missing previous Sprint entries to the Changelog


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/07b68d39
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/07b68d39
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/07b68d39

Branch: refs/heads/master
Commit: 07b68d39d61966c30e0156aeb986dcdf9e1e66ec
Parents: c03515b
Author: Benoit Tellier <bt...@linagora.com>
Authored: Mon Dec 10 11:21:04 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:46:50 2018 +0700

----------------------------------------------------------------------
 CHANGELOG.md | 5 +++++
 1 file changed, 5 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/07b68d39/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67725c3..d9c07a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,10 +8,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 ### Added
 - Metrics for BlobStore
 - New Guice product using Cassandra RabbitMQ ElasticSearch, OpenStack Swift and optional LDAP dependency (experimental)
+- JPA SMTP dockerFile contributed by [matzepan](https://github.com/matzepan)
+- Listing healthchecks, thanks to [Madhu Bhat](https://github.com/kratostaine)
+- Configuring the ElasticSearch clusterName
 
 ### Fixed
 - MAILBOX-350 Potential invalid UID <-> MSN mapping upon IMAP COPY
 - Possibility to better zoom in Grafana boards
+- default ElasticSearch shards & replica configured values
+- Move & copy batch sizes are now loaded from configuration
 
 ### Changed
 - WebAdmin ReIndexing API had been reworked


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


[3/6] james-project git commit: JAMES-2555 Document WebAdmin ReIndexing API in changeLog and upgrade instructions

Posted by bt...@apache.org.
JAMES-2555 Document WebAdmin ReIndexing API in changeLog and upgrade instructions


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/6e589fc7
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/6e589fc7
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/6e589fc7

Branch: refs/heads/master
Commit: 6e589fc78f8b475f07d6935fd17700eccf5a6e91
Parents: 24c29fa
Author: Benoit Tellier <bt...@linagora.com>
Authored: Thu Dec 6 10:28:40 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:46:49 2018 +0700

----------------------------------------------------------------------
 CHANGELOG.md            |  1 +
 upgrade-instructions.md | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/6e589fc7/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e088902..68fe832 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 - Multiple libraries updates
 - Migration from Cassandra 2 to Cassandra 3
 - Mail::getSender was deprecated. Mail::getMaybeSender offers better Null Sender support. Java 8 default API method was used to not break compatibility.
+- WebAdmin ReIndexing API had been reworked
 
 ### Deprecated
  - HBase and JCR components (mailbox and server/data). This will be removed as part of 3.3.0. If you have development skills, and are willing to maintain these components, please reach us.

http://git-wip-us.apache.org/repos/asf/james-project/blob/6e589fc7/upgrade-instructions.md
----------------------------------------------------------------------
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index 2de7d94..0a1fd3c 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -14,6 +14,22 @@ Note: this section is in progress. It will be updated during all the development
 
 Changes to apply between 3.2.x and 3.3.x will be reported here.
 
+Change list:
+
+ - [Changes in WebAdmin reIndexing API](#changes-in-webadmin-reindexing-api)
+
+### Changes in WebAdmin reIndexing API
+
+Date: 05/12/2018
+
+SHA-1: 985b9a4a75bfa75c331cba6cbf835c043185dbdb
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-2555
+
+We made this API introduced in James 3.2.0 a bit more REST friendly. If you developed tools using this API, you will need to update them.
+
+For more details please refer to [the latest WebAdmin documentation](https://github.com/apache/james-project/blob/master/src/site/markdown/server/manage-webadmin.md#ReIndexing).
+
 ## 3.2.0 version
 
 Changes to apply between 3.1.0 and 3.2.0 had been reported here.


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


[2/6] james-project git commit: Update changelog

Posted by bt...@apache.org.
Update changelog


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/24c29fad
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/24c29fad
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/24c29fad

Branch: refs/heads/master
Commit: 24c29fad7932abe0f8a0037f8ff84a93b55fa32c
Parents: e6de4b4
Author: Benoit Tellier <bt...@linagora.com>
Authored: Wed Dec 5 11:08:38 2018 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Thu Dec 13 09:46:49 2018 +0700

----------------------------------------------------------------------
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/24c29fad/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7164312..e088902 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 - Mailet DOC: Exclude from documentation annotation thanks to [mschnitzler](https://github.com/mschnitzler)
 - `cassandra.pooling.max.queue.size` configuration option Thanks to [matzepan](https://github.com/matzepan)
 - `RecipentDomainIs` and `SenderDomainIs` matchers by [athulyaraj](https://github.com/athulyaraj)
+- Metrics for BlobStore
+- New Guice product using Cassandra RabbitMQ ElasticSearch, OpenStack Swift and optional LDAP dependency (experiemental)
+
+### Fixed
+- MAILBOX-350 Potential invalid UID <-> MSN mapping upon IMAP COPY
+- Possibility to better zoom in Grafana boards
 
 ### Changed
 - Multiple libraries updates


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