MAILBOX-331 Implement Quota Threshold notifier as an EventSourcing project

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

Branch: refs/heads/master
Commit: 0e437d2593d3b666de51af493283d1d792761d35
Parents: b035747
Author: benwa <[email protected]>
Authored: Thu Apr 26 12:32:37 2018 +0700
Committer: benwa <[email protected]>
Committed: Tue May 8 15:42:03 2018 +0700

----------------------------------------------------------------------
 .../apache/james/mailbox/MailboxListener.java   |  14 +-
 .../james/mailbox/MailboxManagerTest.java       |  14 +-
 mailbox/memory/pom.xml                          |   1 +
 mailbox/plugin/quota-mailing-memory/pom.xml     |  96 +++++++
 .../james/eventsource/InMemoryEventStore.java   | 104 ++++++++
 .../InMemoryEventSourcingSystemTest.java        |  27 ++
 .../InMemoryEventStoreExtension.java            |  39 +++
 .../eventsourcing/InMemoryEventStoreTest.java   |  27 ++
 ...oryQuotaMailingListenersIntegrationTest.java |  29 +++
 mailbox/plugin/quota-mailing/pom.xml            | 103 ++++++++
 .../apache/james/eventsourcing/AggregateId.java |  24 ++
 .../james/eventsourcing/CommandDispatcher.java  | 115 +++++++++
 .../org/apache/james/eventsourcing/Event.java   |  32 +++
 .../apache/james/eventsourcing/EventBus.java    |  61 +++++
 .../org/apache/james/eventsourcing/EventId.java |  78 ++++++
 .../eventsourcing/EventSourcingSystem.java      |  36 +++
 .../apache/james/eventsourcing/EventStore.java  | 114 +++++++++
 .../apache/james/eventsourcing/Subscriber.java  |  24 ++
 .../QuotaMailingListenerConfiguration.java      |  74 ++++++
 .../mailing/aggregates/UserQuotaThresholds.java | 163 ++++++++++++
 .../commands/DetectThresholdCrossing.java       |  77 ++++++
 .../DetectThresholdCrossingHandler.java         |  59 +++++
 .../events/QuotaThresholdChangedEvent.java      |  75 ++++++
 .../QuotaThresholdCrossingListener.java         |  71 ++++++
 .../subscribers/QuotaThresholdMailer.java       |  79 ++++++
 .../subscribers/QuotaThresholdNotice.java       | 199 +++++++++++++++
 .../mailbox/quota/model/HistoryEvolution.java   | 111 ++++++++
 .../mailbox/quota/model/QuotaThreshold.java     |  99 ++++++++
 .../quota/model/QuotaThresholdChange.java       |  71 ++++++
 .../quota/model/QuotaThresholdHistory.java      | 109 ++++++++
 .../mailbox/quota/model/QuotaThresholds.java    |  58 +++++
 .../eventsourcing/DataCollectorSubscriber.java  |  46 ++++
 .../apache/james/eventsourcing/EventIdTest.java |  85 +++++++
 .../eventsourcing/EventSourcingSystemTest.java  | 251 +++++++++++++++++++
 .../james/eventsourcing/EventStoreTest.java     |  75 ++++++
 .../apache/james/eventsourcing/HistoryTest.java |  87 +++++++
 .../james/eventsourcing/TestAggregateId.java    |  64 +++++
 .../apache/james/eventsourcing/TestEvent.java   |  82 ++++++
 .../aggregates/UserQuotaThresholdsTest.java     |  47 ++++
 .../commands/DetectThresholdCrossingTest.java   |  33 +++
 .../QuotaThresholdListenersTestSystem.java      |  58 +++++
 .../QuotaThresholdMailingIntegrationTest.java   | 217 ++++++++++++++++
 .../subscribers/QuotaThresholdNoticeTest.java   | 202 +++++++++++++++
 .../quota/model/HistoryEvolutionTest.java       | 104 ++++++++
 .../quota/model/QuotaThresholdChangeTest.java   |  59 +++++
 .../quota/model/QuotaThresholdFixture.java      | 149 +++++++++++
 .../quota/model/QuotaThresholdHistoryTest.java  |  87 +++++++
 .../mailbox/quota/model/QuotaThresholdTest.java | 141 +++++++++++
 .../quota/model/QuotaThresholdsTest.java        |  83 ++++++
 mailbox/pom.xml                                 |   2 +
 .../store/event/MailboxEventDispatcher.java     |   8 +-
 .../apache/mailet/base/MailAddressFixture.java  |   1 +
 .../mailet/base/test/FakeMailContext.java       |  18 +-
 pom.xml                                         |  11 +
 54 files changed, 4071 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java
----------------------------------------------------------------------
diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java 
b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java
index de16426..664a107 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java
@@ -20,6 +20,7 @@
 package org.apache.james.mailbox;
 
 import java.io.Serializable;
+import java.time.Instant;
 import java.util.List;
 import java.util.Objects;
 
@@ -71,12 +72,14 @@ public interface MailboxListener {
         private final QuotaRoot quotaRoot;
         private final Quota<QuotaCount> countQuota;
         private final Quota<QuotaSize> sizeQuota;
+        private final Instant instant;
 
-        public QuotaUsageUpdatedEvent(MailboxSession session, QuotaRoot 
quotaRoot, Quota<QuotaCount> countQuota, Quota<QuotaSize> sizeQuota) {
+        public QuotaUsageUpdatedEvent(MailboxSession session, QuotaRoot 
quotaRoot, Quota<QuotaCount> countQuota, Quota<QuotaSize> sizeQuota, Instant 
instant) {
             this.session = session;
             this.quotaRoot = quotaRoot;
             this.countQuota = countQuota;
             this.sizeQuota = sizeQuota;
+            this.instant = instant;
         }
 
         @Override
@@ -97,6 +100,10 @@ public interface MailboxListener {
             return quotaRoot;
         }
 
+        public Instant getInstant() {
+            return instant;
+        }
+
         @Override
         public final boolean equals(Object o) {
             if (o instanceof QuotaUsageUpdatedEvent) {
@@ -105,14 +112,15 @@ public interface MailboxListener {
                 return Objects.equals(this.session, that.session)
                     && Objects.equals(this.quotaRoot, that.quotaRoot)
                     && Objects.equals(this.countQuota, that.countQuota)
-                    && Objects.equals(this.sizeQuota, that.sizeQuota);
+                    && Objects.equals(this.sizeQuota, that.sizeQuota)
+                    && Objects.equals(this.instant, that.instant);
             }
             return false;
         }
 
         @Override
         public final int hashCode() {
-            return Objects.hash(session, quotaRoot, countQuota, sizeQuota);
+            return Objects.hash(session, quotaRoot, countQuota, sizeQuota, 
instant);
         }
 
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java 
b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
index 93573f1..cda734b 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
@@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThatCode;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.util.List;
 import java.util.Optional;
 
@@ -928,17 +929,8 @@ public abstract class MailboxManagerTest {
                 .build(message), session);
 
         assertThat(listener.getEvents())
-            .contains(new MailboxListener.QuotaUsageUpdatedEvent(
-                session,
-                QuotaRoot.quotaRoot("#private&" + USER_1, Optional.empty()),
-                Quota.<QuotaCount>builder()
-                    .used(QuotaCount.count(1))
-                    .computedLimit(QuotaCount.unlimited())
-                    .build(),
-                Quota.<QuotaSize>builder()
-                    .used(QuotaSize.size(85))
-                    .computedLimit(QuotaSize.unlimited())
-                    .build()));
+            .filteredOn(event -> event instanceof 
MailboxListener.QuotaUsageUpdatedEvent)
+            .isNotEmpty();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/memory/pom.xml
----------------------------------------------------------------------
diff --git a/mailbox/memory/pom.xml b/mailbox/memory/pom.xml
index 3994681..21b245c 100644
--- a/mailbox/memory/pom.xml
+++ b/mailbox/memory/pom.xml
@@ -64,6 +64,7 @@
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>nl.jqno.equalsverifier</groupId>

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/pom.xml
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing-memory/pom.xml 
b/mailbox/plugin/quota-mailing-memory/pom.xml
new file mode 100644
index 0000000..612ed6a
--- /dev/null
+++ b/mailbox/plugin/quota-mailing-memory/pom.xml
@@ -0,0 +1,96 @@
+<?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.1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>quota-mailing-memory</artifactId>
+    <name>Apache James :: Mailbox :: Plugin :: Quota Mailing :: Memory</name>
+    <description>Apache James Mailbox memory implementation of Quota mailing 
listener</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-quota-mailing</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-quota-mailing</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-store</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mailet-base</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>james-server-data-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </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.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/src/main/java/org/apache/james/eventsource/InMemoryEventStore.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing-memory/src/main/java/org/apache/james/eventsource/InMemoryEventStore.java
 
b/mailbox/plugin/quota-mailing-memory/src/main/java/org/apache/james/eventsource/InMemoryEventStore.java
new file mode 100644
index 0000000..91cdbb9
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing-memory/src/main/java/org/apache/james/eventsource/InMemoryEventStore.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * 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.eventsource;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.james.eventsourcing.AggregateId;
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.EventStore;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+public class InMemoryEventStore implements EventStore {
+
+    private final ConcurrentHashMap<AggregateId, History> store;
+
+    public InMemoryEventStore() {
+        this.store = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public void appendAll(List<Event> events) {
+        if (events.isEmpty()) {
+            return;
+        }
+        AggregateId aggregateId = getAggregateId(events);
+
+        if (!store.containsKey(aggregateId)) {
+            appendToEmptyHistory(aggregateId, events);
+        } else {
+            appendToExistingHistory(aggregateId, events);
+        }
+    }
+
+    private AggregateId getAggregateId(List<? extends Event> events) {
+        Preconditions.checkArgument(!events.isEmpty());
+        Preconditions.checkArgument(belongsToSameAggregate(events));
+        return events.stream()
+            .map(Event::getAggregateId)
+            .findFirst()
+            .get();
+    }
+
+    private boolean belongsToSameAggregate(List<? extends Event> events) {
+        return events.stream()
+            .map(Event::getAggregateId)
+            .distinct()
+            .limit(2)
+            .count() <= 1;
+    }
+
+    private void appendToEmptyHistory(AggregateId aggregateId, List<Event> 
events) {
+        History newHistory = History.of(events);
+
+        History previousHistory = store.putIfAbsent(aggregateId, newHistory);
+        if (previousHistory != null) {
+            throw new EventStore.EventStoreFailedException();
+        }
+    }
+
+    private void appendToExistingHistory(AggregateId aggregateId, List<? 
extends Event> events) {
+        History currentHistory = store.get(aggregateId);
+        List<Event> updatedEvents = updatedEvents(currentHistory, events);
+        History updatedHistory = History.of(updatedEvents);
+
+        boolean isReplaced = store.replace(aggregateId, currentHistory, 
updatedHistory);
+        if (!isReplaced) {
+            throw new EventStore.EventStoreFailedException();
+        }
+    }
+
+    private List<Event> updatedEvents(History currentHistory, List<? extends 
Event> newEvents) {
+        return ImmutableList.<Event>builder()
+            .addAll(currentHistory.getEvents())
+            .addAll(newEvents)
+            .build();
+    }
+
+    @Override
+    public History getEventsOfAggregate(AggregateId aggregateId) {
+        return Optional.ofNullable(store.get(aggregateId))
+            .orElse(History.empty());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventSourcingSystemTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventSourcingSystemTest.java
 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventSourcingSystemTest.java
new file mode 100644
index 0000000..c2f9935
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventSourcingSystemTest.java
@@ -0,0 +1,27 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(InMemoryEventStoreExtension.class)
+public class InMemoryEventSourcingSystemTest implements 
EventSourcingSystemTest {
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreExtension.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreExtension.java
 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreExtension.java
new file mode 100644
index 0000000..c4b345e
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreExtension.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import org.apache.james.eventsource.InMemoryEventStore;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+public class InMemoryEventStoreExtension implements ParameterResolver {
+
+    @Override
+    public boolean supportsParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
+        return (parameterContext.getParameter().getType() == EventStore.class);
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
+        return new InMemoryEventStore();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreTest.java
 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreTest.java
new file mode 100644
index 0000000..eddc2d0
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/eventsourcing/InMemoryEventStoreTest.java
@@ -0,0 +1,27 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(InMemoryEventStoreExtension.class)
+public class InMemoryEventStoreTest implements EventStoreTest {
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/mailbox/quota/memory/listeners/InMemoryQuotaMailingListenersIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/mailbox/quota/memory/listeners/InMemoryQuotaMailingListenersIntegrationTest.java
 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/mailbox/quota/memory/listeners/InMemoryQuotaMailingListenersIntegrationTest.java
new file mode 100644
index 0000000..4b17682
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing-memory/src/test/java/org/apache/james/mailbox/quota/memory/listeners/InMemoryQuotaMailingListenersIntegrationTest.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.mailbox.quota.memory.listeners;
+
+import org.apache.james.eventsourcing.InMemoryEventStoreExtension;
+import 
org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdMailingIntegrationTest;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(InMemoryEventStoreExtension.class)
+public class InMemoryQuotaMailingListenersIntegrationTest implements 
QuotaThresholdMailingIntegrationTest {
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/pom.xml
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/pom.xml 
b/mailbox/plugin/quota-mailing/pom.xml
new file mode 100644
index 0000000..480bae9
--- /dev/null
+++ b/mailbox/plugin/quota-mailing/pom.xml
@@ -0,0 +1,103 @@
+<?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.1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>apache-james-mailbox-quota-mailing</artifactId>
+    <name>Apache James :: Mailbox :: Plugin :: Quota Mailing</name>
+    <description>Apache James Mailbox Quota mailing listener</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-store</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-mailet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mailet-base</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>james-server-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>james-server-data-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </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.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/AggregateId.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/AggregateId.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/AggregateId.java
new file mode 100644
index 0000000..18c6224
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/AggregateId.java
@@ -0,0 +1,24 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+public interface AggregateId {
+    String asAggregateKey();
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
new file mode 100644
index 0000000..ef5bfd4
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
@@ -0,0 +1,115 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
+import javax.inject.Inject;
+
+import com.github.steveash.guavate.Guavate;
+
+public class CommandDispatcher {
+
+    private static final int MAX_RETRY = 10;
+
+    public interface Command {
+    }
+
+    public class UnknownCommandException extends RuntimeException {
+        private final Command command;
+
+        public UnknownCommandException(Command command) {
+            super(String.format("Unknown command %s", command));
+            this.command = command;
+        }
+
+        public Command getCommand() {
+            return command;
+        }
+    }
+
+    public class TooManyRetries extends RuntimeException {
+        private final Command command;
+        private final int retries;
+
+
+        public TooManyRetries(Command command, int retries) {
+            super(String.format("Too much retries for command %s. Store 
failure after %d retries", command, retries));
+            this.command = command;
+            this.retries = retries;
+        }
+
+
+        public Command getCommand() {
+            return command;
+        }
+
+        public int getRetries() {
+            return retries;
+        }
+    }
+
+    public interface CommandHandler<C extends Command> {
+        Class<C> handledClass();
+
+        List<? extends Event> handle(C c);
+    }
+
+    private final EventBus eventBus;
+    private final Map<Class, CommandHandler> handlers;
+
+    @Inject
+    public CommandDispatcher(EventBus eventBus, Collection<CommandHandler> 
handlers) {
+        this.eventBus = eventBus;
+        this.handlers = handlers.stream()
+            .collect(Guavate.toImmutableMap(CommandHandler::handledClass, 
handler -> handler));
+    }
+
+    public void dispatch(Command c) {
+        trySeveralTimes(() -> tryDispatch(c))
+            .orElseThrow(() -> new TooManyRetries(c, MAX_RETRY));
+    }
+
+    public Optional<Integer> trySeveralTimes(Supplier<Boolean> singleTry) {
+        return IntStream.range(0, MAX_RETRY)
+            .boxed()
+            .filter(any -> singleTry.get())
+            .findFirst();
+    }
+
+    private boolean tryDispatch(Command c) {
+        try {
+            List<Event> events =
+                Optional.ofNullable(handlers.get(c.getClass()))
+                    .map(f -> f.handle(c))
+                    .orElseThrow(() -> new UnknownCommandException(c));
+
+            eventBus.publish(events);
+            return true;
+        } catch (EventStore.EventStoreFailedException e) {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Event.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Event.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Event.java
new file mode 100644
index 0000000..7b3bc00
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Event.java
@@ -0,0 +1,32 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+public interface Event extends Comparable<Event> {
+
+    EventId eventId();
+
+    AggregateId getAggregateId();
+
+    @Override
+    default int compareTo(Event o) {
+        return eventId().compareTo(o.eventId());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventBus.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventBus.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventBus.java
new file mode 100644
index 0000000..067d432
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventBus.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+
+public class EventBus {
+
+    public static final Logger LOGGER = 
LoggerFactory.getLogger(EventBus.class);
+    private final EventStore eventStore;
+    private final Set<Subscriber> subscribers;
+
+    @Inject
+    public EventBus(EventStore eventStore, Set<Subscriber> subscribers) {
+        this.eventStore = eventStore;
+        this.subscribers = ImmutableSet.copyOf(subscribers);
+    }
+
+    public void publish(List<Event> events) throws 
EventStore.EventStoreFailedException {
+        eventStore.appendAll(events);
+        events.stream()
+            .flatMap(event -> subscribers.stream().map(subscriber -> 
Pair.of(event, subscriber)))
+            .forEach(this::handle);
+    }
+
+    public void handle(Pair<Event, Subscriber> pair) {
+        Subscriber subscriber = pair.getRight();
+        Event event = pair.getLeft();
+        try {
+            subscriber.handle(event);
+        } catch (Exception e) {
+            LOGGER.error("Error while calling {} for {}", subscriber, event, 
e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventId.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventId.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventId.java
new file mode 100644
index 0000000..1d636f6
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventId.java
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class EventId implements Comparable<EventId> {
+
+    public static EventId first() {
+        return new EventId(0);
+    }
+
+    private final long value;
+
+    private EventId(long value) {
+        Preconditions.checkArgument(value >= 0, "EventId can not be negative");
+        this.value = value;
+    }
+
+    public EventId next() {
+        return new EventId(value + 1);
+    }
+
+    public Optional<EventId> previous() {
+        if (value > 0) {
+            return Optional.of(new EventId(value - 1));
+        }
+        return Optional.empty();
+    }
+
+    @Override
+    public int compareTo(EventId o) {
+        return Long.compare(value, o.value);
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof EventId) {
+            EventId eventId = (EventId) o;
+
+            return Objects.equals(this.value, eventId.value);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(value);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("value", value)
+            .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
new file mode 100644
index 0000000..e627224
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import java.util.Set;
+
+public class EventSourcingSystem {
+    private final EventBus eventBus;
+    private final CommandDispatcher commandDispatcher;
+
+    public EventSourcingSystem(Set<CommandDispatcher.CommandHandler> handlers, 
Set<Subscriber> subscribers, EventStore eventStore) {
+        this.eventBus = new EventBus(eventStore, subscribers);
+        this.commandDispatcher = new CommandDispatcher(eventBus, handlers);
+    }
+
+    public void dispatch(CommandDispatcher.Command c) {
+        commandDispatcher.dispatch(c);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventStore.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventStore.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventStore.java
new file mode 100644
index 0000000..1ba8028
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/EventStore.java
@@ -0,0 +1,114 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+public interface EventStore {
+
+    class EventStoreFailedException extends RuntimeException {
+
+    }
+
+    class History {
+        public static History empty() {
+            return new History(ImmutableList.of());
+        }
+
+        public static History of(List<Event> events) {
+            return new History(ImmutableList.copyOf(events));
+        }
+
+        public static History of(Event... events) {
+            return of(ImmutableList.copyOf(events));
+        }
+
+        private final List<Event> events;
+
+        private History(List<Event> events) {
+            if (hasEventIdDuplicates(events)) {
+                throw new EventStoreFailedException();
+            }
+            this.events = events;
+        }
+
+        public boolean hasEventIdDuplicates(List<Event> events) {
+            Set<EventId> eventIds = events.stream()
+                .map(Event::eventId)
+                .collect(Guavate.toImmutableSet());
+
+            return eventIds.size() != events.size();
+        }
+
+        public Optional<EventId> getVersion() {
+            return events.stream()
+                .map(Event::eventId)
+                .max(Comparator.naturalOrder());
+        }
+
+        public List<Event> getEvents() {
+            return events;
+        }
+
+        public EventId getNextEventId() {
+            return getVersion()
+                .map(EventId::next)
+                .orElse(EventId.first());
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof History) {
+                History history = (History) o;
+
+                return Objects.equals(this.events, history.events);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(events);
+        }
+    }
+
+    default void append(Event event) {
+        appendAll(ImmutableList.of(event));
+    }
+
+    default void appendAll(Event... events) {
+        appendAll(ImmutableList.copyOf(events));
+    }
+
+    /**
+     * This method should check that no input event has an id already stored 
and throw otherwise
+     * It should also check that all events belong to the same aggregate
+     */
+    void appendAll(List<Event> events);
+
+    History getEventsOfAggregate(AggregateId aggregateId);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Subscriber.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Subscriber.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Subscriber.java
new file mode 100644
index 0000000..42a804d
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/eventsourcing/Subscriber.java
@@ -0,0 +1,24 @@
+/****************************************************************
+ * 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.eventsourcing;
+
+public interface Subscriber {
+    void handle(Event event);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
new file mode 100644
index 0000000..25d504c
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing;
+
+import java.time.Duration;
+import java.util.Objects;
+
+import org.apache.james.mailbox.quota.model.QuotaThresholds;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+public class QuotaMailingListenerConfiguration {
+    public static QuotaMailingListenerConfiguration DEFAULT = new 
QuotaMailingListenerConfiguration(
+        new QuotaThresholds(ImmutableList.of()),
+        Duration.ofDays(1));
+
+    private final QuotaThresholds thresholds;
+    private final Duration gracePeriod;
+
+    public QuotaMailingListenerConfiguration(QuotaThresholds thresholds, 
Duration gracePeriod) {
+        this.thresholds = thresholds;
+        this.gracePeriod = gracePeriod;
+    }
+
+    public QuotaThresholds getThresholds() {
+        return thresholds;
+    }
+
+    public Duration getGracePeriod() {
+        return gracePeriod;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof QuotaMailingListenerConfiguration) {
+            QuotaMailingListenerConfiguration that = 
(QuotaMailingListenerConfiguration) o;
+
+            return Objects.equals(this.thresholds, that.thresholds)
+                && Objects.equals(this.gracePeriod, that.gracePeriod);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(thresholds, gracePeriod);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("thresholds", thresholds)
+            .add("gracePeriod", gracePeriod)
+            .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/aggregates/UserQuotaThresholds.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/aggregates/UserQuotaThresholds.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/aggregates/UserQuotaThresholds.java
new file mode 100644
index 0000000..8290baf
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/aggregates/UserQuotaThresholds.java
@@ -0,0 +1,163 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.aggregates;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.james.core.User;
+import org.apache.james.eventsourcing.AggregateId;
+import org.apache.james.eventsourcing.EventStore;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+import 
org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration;
+import org.apache.james.mailbox.quota.mailing.commands.DetectThresholdCrossing;
+import 
org.apache.james.mailbox.quota.mailing.events.QuotaThresholdChangedEvent;
+import org.apache.james.mailbox.quota.model.HistoryEvolution;
+import org.apache.james.mailbox.quota.model.QuotaThresholdChange;
+import org.apache.james.mailbox.quota.model.QuotaThresholdHistory;
+import org.apache.james.mailbox.quota.model.QuotaThresholds;
+import org.apache.james.util.OptionalUtils;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+public class UserQuotaThresholds {
+
+    public static class Id implements AggregateId {
+
+        public static Id from(User user) {
+            return new Id(user);
+        }
+
+        private final User user;
+
+        private Id(User user) {
+            this.user = user;
+        }
+
+        public User getUser() {
+            return user;
+        }
+
+        @Override
+        public String asAggregateKey() {
+            return "QuotaThreasholdEvents-" + user.asString();
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Id) {
+                Id id = (Id) o;
+
+                return Objects.equals(this.user, id.user);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(user);
+        }
+    }
+
+    public static UserQuotaThresholds fromEvents(Id aggregateId, 
EventStore.History history) {
+        return new UserQuotaThresholds(aggregateId, history);
+    }
+
+    private final Id aggregateId;
+    private final EventStore.History history;
+    private final List<QuotaThresholdChangedEvent> events;
+
+    private UserQuotaThresholds(Id aggregateId, EventStore.History history) {
+        this.aggregateId = aggregateId;
+        this.history = history;
+        this.events = history.getEvents().stream()
+            .map(QuotaThresholdChangedEvent.class::cast)
+            .collect(Collectors.toList());
+    }
+
+    public List<QuotaThresholdChangedEvent> 
detectThresholdCrossing(QuotaMailingListenerConfiguration configuration,
+                                                                    
DetectThresholdCrossing command) {
+
+        List<QuotaThresholdChangedEvent> events = generateEvents(
+            configuration.getThresholds(),
+            configuration.getGracePeriod(),
+            command.getCountQuota(),
+            command.getSizeQuota(),
+            command.getInstant());
+        events.forEach(this::apply);
+        return events;
+    }
+
+    private List<QuotaThresholdChangedEvent> generateEvents(QuotaThresholds 
configuration, Duration gracePeriod, Quota<QuotaCount> countQuota, 
Quota<QuotaSize> sizeQuota, Instant now) {
+        QuotaThresholdChange countThresholdChange = new 
QuotaThresholdChange(configuration.highestExceededThreshold(countQuota), now);
+        QuotaThresholdChange sizeThresholdChange = new 
QuotaThresholdChange(configuration.highestExceededThreshold(sizeQuota), now);
+
+        HistoryEvolution countHistoryEvolution = computeCountHistory()
+            .compareWithCurrentThreshold(countThresholdChange, gracePeriod);
+        HistoryEvolution sizeHistoryEvolution = computeSizeHistory()
+            .compareWithCurrentThreshold(sizeThresholdChange, gracePeriod);
+
+        return generateEvents(countHistoryEvolution, sizeHistoryEvolution, 
countQuota, sizeQuota);
+    }
+
+    private QuotaThresholdHistory computeSizeHistory() {
+        return new QuotaThresholdHistory(
+            events.stream()
+                .map(QuotaThresholdChangedEvent::getSizeHistoryEvolution)
+                .map(HistoryEvolution::getThresholdChange)
+                .flatMap(OptionalUtils::toStream)
+                .collect(Guavate.toImmutableList()));
+    }
+
+    private QuotaThresholdHistory computeCountHistory() {
+        return new QuotaThresholdHistory(
+            events.stream()
+                .map(QuotaThresholdChangedEvent::getCountHistoryEvolution)
+                .map(HistoryEvolution::getThresholdChange)
+                .flatMap(OptionalUtils::toStream)
+                .collect(Guavate.toImmutableList()));
+    }
+
+    private List<QuotaThresholdChangedEvent> generateEvents(HistoryEvolution 
countHistoryEvolution, HistoryEvolution sizeHistoryEvolution, Quota<QuotaCount> 
countQuota, Quota<QuotaSize> sizeQuota) {
+        if (countHistoryEvolution.isChange() || 
sizeHistoryEvolution.isChange()) {
+            return ImmutableList.of(
+                new QuotaThresholdChangedEvent(
+                    history.getNextEventId(),
+                    sizeHistoryEvolution,
+                    countHistoryEvolution,
+                    sizeQuota,
+                    countQuota,
+                    aggregateId));
+        }
+
+        return ImmutableList.of();
+    }
+
+    private void apply(QuotaThresholdChangedEvent event) {
+        events.add(event);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossing.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossing.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossing.java
new file mode 100644
index 0000000..0de0023
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossing.java
@@ -0,0 +1,77 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.commands;
+
+import java.time.Instant;
+import java.util.Objects;
+
+import org.apache.james.core.User;
+import org.apache.james.eventsourcing.CommandDispatcher;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+
+public class DetectThresholdCrossing implements CommandDispatcher.Command {
+
+    private final User user;
+    private final Quota<QuotaCount> countQuota;
+    private final Quota<QuotaSize> sizeQuota;
+    private final Instant instant;
+
+    public DetectThresholdCrossing(User user, Quota<QuotaCount> countQuota, 
Quota<QuotaSize> sizeQuota, Instant instant) {
+        this.user = user;
+        this.countQuota = countQuota;
+        this.sizeQuota = sizeQuota;
+        this.instant = instant;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public Quota<QuotaCount> getCountQuota() {
+        return countQuota;
+    }
+
+    public Quota<QuotaSize> getSizeQuota() {
+        return sizeQuota;
+    }
+
+    public Instant getInstant() {
+        return instant;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof DetectThresholdCrossing) {
+            DetectThresholdCrossing that = (DetectThresholdCrossing) o;
+
+            return Objects.equals(this.user, that.user)
+                && Objects.equals(this.countQuota, that.countQuota)
+                && Objects.equals(this.sizeQuota, that.sizeQuota);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(user, countQuota, sizeQuota);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossingHandler.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossingHandler.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossingHandler.java
new file mode 100644
index 0000000..39b2013
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/commands/DetectThresholdCrossingHandler.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.commands;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.james.eventsourcing.CommandDispatcher;
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.EventStore;
+import 
org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration;
+import org.apache.james.mailbox.quota.mailing.aggregates.UserQuotaThresholds;
+
+public class DetectThresholdCrossingHandler implements 
CommandDispatcher.CommandHandler<DetectThresholdCrossing> {
+
+    private final EventStore eventStore;
+    private final QuotaMailingListenerConfiguration 
quotaMailingListenerConfiguration;
+
+    @Inject
+    public DetectThresholdCrossingHandler(EventStore eventStore, 
QuotaMailingListenerConfiguration quotaMailingListenerConfiguration) {
+        this.eventStore = eventStore;
+        this.quotaMailingListenerConfiguration = 
quotaMailingListenerConfiguration;
+    }
+
+    @Override
+    public List<? extends Event> handle(DetectThresholdCrossing command) {
+        return loadAggregate(command)
+            .detectThresholdCrossing(quotaMailingListenerConfiguration, 
command);
+    }
+
+    private UserQuotaThresholds loadAggregate(DetectThresholdCrossing command) 
{
+        UserQuotaThresholds.Id aggregateId = 
UserQuotaThresholds.Id.from(command.getUser());
+        EventStore.History history = 
eventStore.getEventsOfAggregate(aggregateId);
+        return UserQuotaThresholds.fromEvents(aggregateId, history);
+    }
+
+    @Override
+    public Class<DetectThresholdCrossing> handledClass() {
+        return DetectThresholdCrossing.class;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/events/QuotaThresholdChangedEvent.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/events/QuotaThresholdChangedEvent.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/events/QuotaThresholdChangedEvent.java
new file mode 100644
index 0000000..3c21a3f
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/events/QuotaThresholdChangedEvent.java
@@ -0,0 +1,75 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.events;
+
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.EventId;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+import org.apache.james.mailbox.quota.mailing.aggregates.UserQuotaThresholds;
+import org.apache.james.mailbox.quota.model.HistoryEvolution;
+
+public class QuotaThresholdChangedEvent implements Event {
+
+    private final EventId eventId;
+    private final HistoryEvolution sizeHistoryEvolution;
+    private final HistoryEvolution countHistoryEvolution;
+    private final Quota<QuotaSize> sizeQuota;
+    private final Quota<QuotaCount> countQuota;
+    private final UserQuotaThresholds.Id aggregateId;
+
+    public QuotaThresholdChangedEvent(EventId eventId, HistoryEvolution 
sizeHistoryEvolution, HistoryEvolution countHistoryEvolution, Quota<QuotaSize> 
sizeQuota, Quota<QuotaCount> countQuota, UserQuotaThresholds.Id aggregateId) {
+        this.eventId = eventId;
+        this.sizeHistoryEvolution = sizeHistoryEvolution;
+        this.countHistoryEvolution = countHistoryEvolution;
+        this.sizeQuota = sizeQuota;
+        this.countQuota = countQuota;
+        this.aggregateId = aggregateId;
+    }
+
+    public HistoryEvolution getSizeHistoryEvolution() {
+        return sizeHistoryEvolution;
+    }
+
+    public HistoryEvolution getCountHistoryEvolution() {
+        return countHistoryEvolution;
+    }
+
+    public Quota<QuotaSize> getSizeQuota() {
+        return sizeQuota;
+    }
+
+    public Quota<QuotaCount> getCountQuota() {
+        return countQuota;
+    }
+
+    @Override
+    public EventId eventId() {
+        return eventId;
+    }
+
+    @Override
+    public UserQuotaThresholds.Id getAggregateId() {
+        return aggregateId;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/listeners/QuotaThresholdCrossingListener.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/listeners/QuotaThresholdCrossingListener.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/listeners/QuotaThresholdCrossingListener.java
new file mode 100644
index 0000000..a4b1094
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/listeners/QuotaThresholdCrossingListener.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.listeners;
+
+import org.apache.james.core.User;
+import org.apache.james.eventsourcing.EventSourcingSystem;
+import org.apache.james.mailbox.Event;
+import org.apache.james.mailbox.MailboxListener;
+import org.apache.james.mailbox.quota.mailing.commands.DetectThresholdCrossing;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QuotaThresholdCrossingListener implements MailboxListener {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(QuotaThresholdCrossingListener.class);
+
+    private final EventSourcingSystem eventSourcingSystem;
+
+    public QuotaThresholdCrossingListener(EventSourcingSystem 
eventSourcingSystem) {
+        this.eventSourcingSystem = eventSourcingSystem;
+    }
+
+    @Override
+    public ListenerType getType() {
+        return ListenerType.ONCE;
+    }
+
+    @Override
+    public ExecutionMode getExecutionMode() {
+        return ExecutionMode.SYNCHRONOUS;
+    }
+
+    @Override
+    public void event(Event event) {
+        try {
+            if (event instanceof QuotaUsageUpdatedEvent) {
+                handleEvent(getUser(event), (QuotaUsageUpdatedEvent) event);
+            }
+        } catch (Exception e) {
+            LOGGER.error("Can not re-emmit quota threshold events", e);
+        }
+    }
+
+    private void handleEvent(User user, QuotaUsageUpdatedEvent event) {
+        eventSourcingSystem.dispatch(
+            new DetectThresholdCrossing(user, event.getCountQuota(), 
event.getSizeQuota(), event.getInstant()));
+    }
+
+    private User getUser(Event event) {
+        return User.fromUsername(
+            event.getSession()
+                .getUser()
+                .getUserName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/0e437d25/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdMailer.java
----------------------------------------------------------------------
diff --git 
a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdMailer.java
 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdMailer.java
new file mode 100644
index 0000000..2b5cc94
--- /dev/null
+++ 
b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdMailer.java
@@ -0,0 +1,79 @@
+/****************************************************************
+ * 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.mailbox.quota.mailing.subscribers;
+
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.User;
+import org.apache.james.core.builder.MimeMessageBuilder;
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.Subscriber;
+import 
org.apache.james.mailbox.quota.mailing.events.QuotaThresholdChangedEvent;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.mailet.MailetContext;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
+
+public class QuotaThresholdMailer implements Subscriber {
+    private final MailetContext mailetContext;
+    private final UsersRepository usersRepository;
+
+    public QuotaThresholdMailer(MailetContext mailetContext, UsersRepository 
usersRepository) {
+        this.mailetContext = mailetContext;
+        this.usersRepository = usersRepository;
+    }
+
+    @Override
+    public void handle(Event event) {
+        if (event instanceof QuotaThresholdChangedEvent) {
+            handleEvent((QuotaThresholdChangedEvent) event);
+        }
+    }
+
+    private void handleEvent(QuotaThresholdChangedEvent event) {
+        Optional<QuotaThresholdNotice> maybeNotice = 
QuotaThresholdNotice.builder()
+            .countQuota(event.getCountQuota())
+            .sizeQuota(event.getSizeQuota())
+            .countThreshold(event.getCountHistoryEvolution())
+            .sizeThreshold(event.getSizeHistoryEvolution())
+            .build();
+
+        maybeNotice.ifPresent(Throwing.consumer(notice -> sendNotice(notice, 
event.getAggregateId().getUser())));
+    }
+
+    private void sendNotice(QuotaThresholdNotice notice, User user) throws 
UsersRepositoryException, MessagingException {
+        MailAddress sender = mailetContext.getPostmaster();
+        MailAddress recipient = usersRepository.getMailAddressFor(user);
+
+        mailetContext.sendMail(sender, ImmutableList.of(recipient),
+            MimeMessageBuilder.mimeMessageBuilder()
+                .addFrom(sender.asString())
+                .addToRecipient(recipient.asString())
+                .setSubject("Warning: Your email usage just exceeded a 
configured threshold")
+                .setText(notice.generateReport())
+                .build());
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to