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/03/15 09:14:18 UTC

[08/18] james-project git commit: JAMES-2344 implement per domain quota. Wiring to user quota missing

JAMES-2344 implement per domain quota. Wiring to user quota missing


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

Branch: refs/heads/master
Commit: 5191b5fa998e364f08beb203b9a9dc2371da4a7c
Parents: 79876e3
Author: Matthieu Baechler <ma...@apache.org>
Authored: Mon Mar 12 17:24:51 2018 +0100
Committer: benwa <bt...@linagora.com>
Committed: Thu Mar 15 14:40:15 2018 +0700

----------------------------------------------------------------------
 .../org/apache/james/mailbox/model/Quota.java   |   1 +
 .../james/mailbox/quota/MaxQuotaManager.java    |  12 +
 .../cassandra/modules/CassandraQuotaModule.java |  11 +
 .../quota/CassandraPerDomainMaxQuotaDao.java    | 134 ++++++
 .../quota/CassandraPerUserMaxQuotaManager.java  |  37 +-
 .../table/CassandraDomainMaxQuota.java          |  28 ++
 .../cassandra/CassandraTestSystemFixture.java   |   2 +
 .../CassandraPerUserMaxQuotaManagerTest.java    |   1 +
 .../jpa/quota/JPAPerUserMaxQuotaDAO.java        |  55 +++
 .../jpa/quota/JPAPerUserMaxQuotaManager.java    |  31 ++
 .../jpa/quota/model/MaxDomainMessageCount.java  |  52 +++
 .../jpa/quota/model/MaxDomainStorage.java       |  53 +++
 .../james/mailbox/jpa/JPAMailboxFixture.java    |   6 +
 .../quota/InMemoryPerUserMaxQuotaManager.java   |  37 +-
 .../store/quota/FixedMaxQuotaManager.java       |  34 ++
 .../mailbox/store/quota/NoMaxQuotaManager.java  |  30 ++
 .../store/quota/GenericMaxQuotaManagerTest.java |  98 ++++-
 .../cassandra/host/CassandraHostSystem.java     |   2 +
 .../modules/mailbox/CassandraQuotaModule.java   |   2 +
 .../webadmin/routes/DomainQuotaRoutes.java      | 297 +++++++++++++
 .../webadmin/routes/DomainQuotaService.java     |  81 ++++
 .../webadmin/routes/DomainQuotaRoutesTest.java  | 430 +++++++++++++++++++
 22 files changed, 1430 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java
index dd82bd2..cabd05a 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java
@@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableMap;
 public class Quota<T extends QuotaValue<T>> {
 
     public enum Scope {
+        Domain,
         Global,
         User
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/api/src/main/java/org/apache/james/mailbox/quota/MaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/quota/MaxQuotaManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/quota/MaxQuotaManager.java
index 81814cc..830a81a 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/quota/MaxQuotaManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/quota/MaxQuotaManager.java
@@ -121,4 +121,16 @@ public interface MaxQuotaManager {
     Map<Quota.Scope, QuotaCount> listMaxMessagesDetails(QuotaRoot quotaRoot);
 
     Map<Quota.Scope, QuotaSize> listMaxStorageDetails(QuotaRoot quotaRoot);
+
+    Optional<QuotaCount> getDomainMaxMessage(String domain);
+
+    void setDomainMaxMessage(String domain, QuotaCount count) throws MailboxException;
+
+    void removeDomainMaxMessage(String domain) throws MailboxException;
+
+    void setDomainMaxStorage(String domain, QuotaSize size) throws MailboxException;
+
+    Optional<QuotaSize> getDomainMaxStorage(String domain);
+
+    void removeDomainMaxStorage(String domain) throws MailboxException;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraQuotaModule.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraQuotaModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraQuotaModule.java
index 32a963c..4598fb9 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraQuotaModule.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraQuotaModule.java
@@ -31,6 +31,7 @@ import org.apache.james.backends.cassandra.components.CassandraType;
 import org.apache.james.backends.cassandra.utils.CassandraConstants;
 import org.apache.james.mailbox.cassandra.table.CassandraCurrentQuota;
 import org.apache.james.mailbox.cassandra.table.CassandraDefaultMaxQuota;
+import org.apache.james.mailbox.cassandra.table.CassandraDomainMaxQuota;
 import org.apache.james.mailbox.cassandra.table.CassandraMaxQuota;
 
 import com.datastax.driver.core.schemabuilder.SchemaBuilder;
@@ -63,6 +64,16 @@ public class CassandraQuotaModule implements CassandraModule {
                     .comment("Holds per quota-root limitations. Limitations can concern the number of messages in a quota-root or the total size of a quota-root.")
                     .caching(SchemaBuilder.KeyCaching.ALL,
                         SchemaBuilder.rows(CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION))),
+            new CassandraTable(CassandraDomainMaxQuota.TABLE_NAME,
+                SchemaBuilder.createTable(CassandraDomainMaxQuota.TABLE_NAME)
+                    .ifNotExists()
+                    .addPartitionKey(CassandraDomainMaxQuota.DOMAIN, text())
+                    .addColumn(CassandraDomainMaxQuota.MESSAGE_COUNT, bigint())
+                    .addColumn(CassandraDomainMaxQuota.STORAGE, bigint())
+                    .withOptions()
+                    .comment("Holds per domain limitations. Limitations can concern the number of messages in a quota-root or the total size of a quota-root.")
+                    .caching(SchemaBuilder.KeyCaching.ALL,
+                        SchemaBuilder.rows(CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION))),
             new CassandraTable(CassandraDefaultMaxQuota.TABLE_NAME,
                 SchemaBuilder.createTable(CassandraDefaultMaxQuota.TABLE_NAME)
                     .ifNotExists()

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
new file mode 100644
index 0000000..1f28f4c
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
@@ -0,0 +1,134 @@
+/****************************************************************
+ * 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.cassandra.quota;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.delete;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.cassandra.table.CassandraDomainMaxQuota;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.querybuilder.Delete;
+import com.datastax.driver.core.querybuilder.Insert;
+import com.datastax.driver.core.querybuilder.Select;
+
+public class CassandraPerDomainMaxQuotaDao {
+
+    private final Session session;
+    private final PreparedStatement setMaxStorageStatement;
+    private final PreparedStatement setMaxMessageStatement;
+    private final PreparedStatement getMaxStorageStatement;
+    private final PreparedStatement getMaxMessageStatement;
+    private final PreparedStatement removeMaxStorageStatement;
+    private final PreparedStatement removeMaxMessageStatement;
+
+    @Inject
+    public CassandraPerDomainMaxQuotaDao(Session session) {
+        this.session = session;
+        this.setMaxStorageStatement = session.prepare(setMaxStorageStatement());
+        this.setMaxMessageStatement = session.prepare(setMaxMessageStatement());
+        this.getMaxStorageStatement = session.prepare(getMaxStorageStatement());
+        this.getMaxMessageStatement = session.prepare(getMaxMessageStatement());
+        this.removeMaxStorageStatement = session.prepare(removeMaxStorageStatement());
+        this.removeMaxMessageStatement = session.prepare(removeMaxMessageStatement());
+    }
+
+    private Delete.Where removeMaxMessageStatement() {
+        return delete().column(CassandraDomainMaxQuota.MESSAGE_COUNT)
+            .from(CassandraDomainMaxQuota.TABLE_NAME)
+            .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
+    }
+
+    private Delete.Where removeMaxStorageStatement() {
+        return delete().column(CassandraDomainMaxQuota.STORAGE)
+            .from(CassandraDomainMaxQuota.TABLE_NAME)
+            .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
+    }
+
+    private Select.Where getMaxMessageStatement() {
+        return select(CassandraDomainMaxQuota.MESSAGE_COUNT)
+            .from(CassandraDomainMaxQuota.TABLE_NAME)
+            .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
+    }
+
+    private Select.Where getMaxStorageStatement() {
+        return select(CassandraDomainMaxQuota.STORAGE)
+            .from(CassandraDomainMaxQuota.TABLE_NAME)
+            .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
+    }
+
+    private Insert setMaxMessageStatement() {
+        return insertInto(CassandraDomainMaxQuota.TABLE_NAME)
+            .value(CassandraDomainMaxQuota.DOMAIN, bindMarker())
+            .value(CassandraDomainMaxQuota.MESSAGE_COUNT, bindMarker());
+    }
+
+    private Insert setMaxStorageStatement() {
+        return insertInto(CassandraDomainMaxQuota.TABLE_NAME)
+            .value(CassandraDomainMaxQuota.DOMAIN, bindMarker())
+            .value(CassandraDomainMaxQuota.STORAGE, bindMarker());
+    }
+
+    public void setMaxStorage(String domain, QuotaSize maxStorageQuota) {
+        session.execute(setMaxStorageStatement.bind(domain, QuotaCodec.quotaValueToLong(maxStorageQuota)));
+    }
+
+    public void setMaxMessage(String domain, QuotaCount maxMessageCount) {
+        session.execute(setMaxMessageStatement.bind(domain, QuotaCodec.quotaValueToLong(maxMessageCount)));
+    }
+
+    public Optional<QuotaSize> getMaxStorage(String domain) {
+        ResultSet resultSet = session.execute(getMaxStorageStatement.bind(domain));
+        if (resultSet.isExhausted()) {
+            return Optional.empty();
+        }
+        Long maxStorage = resultSet.one().get(CassandraDomainMaxQuota.STORAGE, Long.class);
+        return QuotaCodec.longToQuotaSize(maxStorage);
+    }
+
+    public Optional<QuotaCount> getMaxMessage(String domain) {
+        ResultSet resultSet = session.execute(getMaxMessageStatement.bind(domain));
+        if (resultSet.isExhausted()) {
+            return Optional.empty();
+        }
+        Long maxMessages = resultSet.one().get(CassandraDomainMaxQuota.MESSAGE_COUNT, Long.class);
+        return QuotaCodec.longToQuotaCount(maxMessages);
+    }
+
+    public void removeMaxMessage(String domain) {
+        session.execute(removeMaxMessageStatement.bind(domain));
+    }
+
+    public void removeMaxStorage(String domain) {
+        session.execute(removeMaxStorageStatement.bind(domain));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
index 05147bd..5b151e3 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.Quota;
 import org.apache.james.mailbox.model.QuotaRoot;
 import org.apache.james.mailbox.quota.MaxQuotaManager;
@@ -38,11 +39,15 @@ import com.github.steveash.guavate.Guavate;
 public class CassandraPerUserMaxQuotaManager implements MaxQuotaManager {
 
     private final CassandraPerUserMaxQuotaDao perUserQuota;
+    private final CassandraPerDomainMaxQuotaDao perDomainQuota;
     private final CassandraDefaultMaxQuotaDao defaultQuota;
 
     @Inject
-    public CassandraPerUserMaxQuotaManager(CassandraPerUserMaxQuotaDao perUserQuota, CassandraDefaultMaxQuotaDao defaultQuota) {
+    public CassandraPerUserMaxQuotaManager(CassandraPerUserMaxQuotaDao perUserQuota,
+                                           CassandraPerDomainMaxQuotaDao domainQuota,
+                                           CassandraDefaultMaxQuotaDao defaultQuota) {
         this.perUserQuota = perUserQuota;
+        this.perDomainQuota = domainQuota;
         this.defaultQuota = defaultQuota;
     }
 
@@ -57,6 +62,36 @@ public class CassandraPerUserMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public void setDomainMaxMessage(String domain, QuotaCount count) {
+        perDomainQuota.setMaxMessage(domain, count);
+    }
+
+    @Override
+    public void setDomainMaxStorage(String domain, QuotaSize size) {
+        perDomainQuota.setMaxStorage(domain, size);
+    }
+
+    @Override
+    public void removeDomainMaxMessage(String domain) throws MailboxException {
+        perDomainQuota.removeMaxMessage(domain);
+    }
+
+    @Override
+    public void removeDomainMaxStorage(String domain) {
+        perDomainQuota.removeMaxStorage(domain);
+    }
+
+    @Override
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        return perDomainQuota.getMaxMessage(domain);
+    }
+
+    @Override
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        return perDomainQuota.getMaxStorage(domain);
+    }
+
+    @Override
     public void removeMaxMessage(QuotaRoot quotaRoot) {
         perUserQuota.removeMaxMessage(quotaRoot);
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraDomainMaxQuota.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraDomainMaxQuota.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraDomainMaxQuota.java
new file mode 100644
index 0000000..66372f8
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraDomainMaxQuota.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.cassandra.table;
+
+public interface CassandraDomainMaxQuota {
+    String TABLE_NAME = "domainMaxQuota";
+
+    String DOMAIN = "domain";
+    String MESSAGE_COUNT = "maxMessageCount";
+    String STORAGE = "maxStorage";
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
index 8fffb69..f396b81 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
@@ -27,6 +27,7 @@ import org.apache.james.mailbox.acl.UnionMailboxACLResolver;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.quota.CassandraCurrentQuotaManager;
 import org.apache.james.mailbox.cassandra.quota.CassandraDefaultMaxQuotaDao;
+import org.apache.james.mailbox.cassandra.quota.CassandraPerDomainMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaManager;
 import org.apache.james.mailbox.quota.CurrentQuotaManager;
@@ -84,6 +85,7 @@ public class CassandraTestSystemFixture {
     public static MaxQuotaManager createMaxQuotaManager(CassandraCluster cassandra) {
         return new CassandraPerUserMaxQuotaManager(
             new CassandraPerUserMaxQuotaDao(cassandra.getConf()),
+            new CassandraPerDomainMaxQuotaDao(cassandra.getConf()),
             new CassandraDefaultMaxQuotaDao(cassandra.getConf()));
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
index 35e495c..0f351ac 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
@@ -38,6 +38,7 @@ public class CassandraPerUserMaxQuotaManagerTest extends GenericMaxQuotaManagerT
         cassandra = CassandraCluster.create(new CassandraQuotaModule(), cassandraServer.getIp(), cassandraServer.getBindingPort());
         return new CassandraPerUserMaxQuotaManager(
             new CassandraPerUserMaxQuotaDao(cassandra.getConf()),
+            new CassandraPerDomainMaxQuotaDao(cassandra.getConf()),
             new CassandraDefaultMaxQuotaDao(cassandra.getConf()));
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaDAO.java
----------------------------------------------------------------------
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaDAO.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaDAO.java
index d29db8a..ab46ff9 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaDAO.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaDAO.java
@@ -28,6 +28,8 @@ import javax.persistence.EntityManagerFactory;
 
 import org.apache.james.mailbox.jpa.quota.model.MaxDefaultMessageCount;
 import org.apache.james.mailbox.jpa.quota.model.MaxDefaultStorage;
+import org.apache.james.mailbox.jpa.quota.model.MaxDomainMessageCount;
+import org.apache.james.mailbox.jpa.quota.model.MaxDomainStorage;
 import org.apache.james.mailbox.jpa.quota.model.MaxUserMessageCount;
 import org.apache.james.mailbox.jpa.quota.model.MaxUserStorage;
 import org.apache.james.mailbox.model.QuotaRoot;
@@ -79,6 +81,42 @@ public class JPAPerUserMaxQuotaDAO {
         return storedValue;
     }
 
+    public void setDomainMaxMessage(String domain, Optional<QuotaCount> count) {
+        entityManager.getTransaction().begin();
+        MaxDomainMessageCount storedValue = getMaxDomainMessageEntity(domain, count);
+        entityManager.persist(storedValue);
+        entityManager.getTransaction().commit();
+    }
+
+
+    public void setDomainMaxStorage(String domain, Optional<QuotaSize> size) {
+        entityManager.getTransaction().begin();
+        MaxDomainStorage storedValue = getMaxDomainStorageEntity(domain, size);
+        entityManager.persist(storedValue);
+        entityManager.getTransaction().commit();
+    }
+
+    private MaxDomainMessageCount getMaxDomainMessageEntity(String domain, Optional<QuotaCount> maxMessageQuota) {
+        MaxDomainMessageCount storedValue = entityManager.find(MaxDomainMessageCount.class, domain);
+        Long value = quotaValueToLong(maxMessageQuota);
+        if (storedValue == null) {
+            return new MaxDomainMessageCount(domain, value);
+        }
+        storedValue.setValue(value);
+        return storedValue;
+    }
+
+    private MaxDomainStorage getMaxDomainStorageEntity(String domain, Optional<QuotaSize> maxStorageQuota) {
+        MaxDomainStorage storedValue = entityManager.find(MaxDomainStorage.class, domain);
+        Long value = quotaValueToLong(maxStorageQuota);
+        if (storedValue == null) {
+            return new MaxDomainStorage(domain, value);
+        }
+        storedValue.setValue(value);
+        return storedValue;
+    }
+
+
     public void setDefaultMaxStorage(Optional<QuotaSize> defaultMaxStorage) {
         entityManager.getTransaction().begin();
         MaxDefaultStorage defaultMaxStorageEntity = getDefaultMaxStorageEntity(defaultMaxStorage);
@@ -145,6 +183,22 @@ public class JPAPerUserMaxQuotaDAO {
         return longToQuotaCount(storedValue.getValue());
     }
 
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        MaxDomainMessageCount storedValue = entityManager.find(MaxDomainMessageCount.class, domain);
+        if (storedValue == null) {
+            return Optional.empty();
+        }
+        return longToQuotaCount(storedValue.getValue());
+    }
+
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        MaxDomainStorage storedValue = entityManager.find(MaxDomainStorage.class, domain);
+        if (storedValue == null) {
+            return Optional.empty();
+        }
+        return longToQuotaSize(storedValue.getValue());
+    }
+
 
     private Long quotaValueToLong(Optional<? extends QuotaValue<?>> maxStorageQuota) {
         return maxStorageQuota.map(value -> {
@@ -172,4 +226,5 @@ public class JPAPerUserMaxQuotaDAO {
         }
         return Optional.of(quotaFactory.apply(value));
     }
+
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaManager.java
index ef509cb..402ea4b 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaManager.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/JPAPerUserMaxQuotaManager.java
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.Quota;
 import org.apache.james.mailbox.model.QuotaRoot;
 import org.apache.james.mailbox.quota.MaxQuotaManager;
@@ -54,6 +55,36 @@ public class JPAPerUserMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public void setDomainMaxMessage(String domain, QuotaCount count) {
+        dao.setDomainMaxMessage(domain, Optional.of(count));
+    }
+
+    @Override
+    public void setDomainMaxStorage(String domain, QuotaSize size) {
+        dao.setDomainMaxStorage(domain, Optional.of(size));
+    }
+
+    @Override
+    public void removeDomainMaxMessage(String domain) throws MailboxException {
+        dao.setDomainMaxMessage(domain, Optional.empty());
+    }
+
+    @Override
+    public void removeDomainMaxStorage(String domain) {
+        dao.setDomainMaxStorage(domain, Optional.empty());
+    }
+
+    @Override
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        return dao.getDomainMaxMessage(domain);
+    }
+
+    @Override
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        return dao.getDomainMaxStorage(domain);
+    }
+
+    @Override
     public void removeMaxMessage(QuotaRoot quotaRoot) {
         dao.setMaxMessage(quotaRoot, Optional.empty());
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainMessageCount.java
----------------------------------------------------------------------
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainMessageCount.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainMessageCount.java
new file mode 100644
index 0000000..eccae16
--- /dev/null
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainMessageCount.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * 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.jpa.quota.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity(name = "MaxDomainMessageCount")
+@Table(name = "JAMES_MAX_DOMAIN_MESSAGE_COUNT")
+public class MaxDomainMessageCount {
+    @Id
+    @Column(name = "DOMAIN")
+    private String domain;
+
+    @Column(name = "VALUE", nullable = true)
+    private Long value;
+
+    public MaxDomainMessageCount(String domain, Long value) {
+        this.domain = domain;
+        this.value = value;
+    }
+
+    public MaxDomainMessageCount() {
+    }
+
+    public Long getValue() {
+        return value;
+    }
+
+    public void setValue(Long value) {
+        this.value = value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainStorage.java
----------------------------------------------------------------------
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainStorage.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainStorage.java
new file mode 100644
index 0000000..3785306
--- /dev/null
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/quota/model/MaxDomainStorage.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.jpa.quota.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity(name = "MaxDomainStorage")
+@Table(name = "JAMES_MAX_DOMAIN_STORAGE")
+public class MaxDomainStorage {
+
+    @Id
+    @Column(name = "DOMAIN")
+    private String domain;
+
+    @Column(name = "VALUE", nullable = true)
+    private Long value;
+
+    public MaxDomainStorage(String domain, Long value) {
+        this.domain = domain;
+        this.value = value;
+    }
+
+    public MaxDomainStorage() {
+    }
+
+    public Long getValue() {
+        return value;
+    }
+
+    public void setValue(Long value) {
+        this.value = value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxFixture.java
----------------------------------------------------------------------
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxFixture.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxFixture.java
index c0f955a..056e5dd 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxFixture.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxFixture.java
@@ -30,6 +30,8 @@ import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMailboxMessage;
 import org.apache.james.mailbox.jpa.quota.model.JpaCurrentQuota;
 import org.apache.james.mailbox.jpa.quota.model.MaxDefaultMessageCount;
 import org.apache.james.mailbox.jpa.quota.model.MaxDefaultStorage;
+import org.apache.james.mailbox.jpa.quota.model.MaxDomainMessageCount;
+import org.apache.james.mailbox.jpa.quota.model.MaxDomainStorage;
 import org.apache.james.mailbox.jpa.quota.model.MaxUserMessageCount;
 import org.apache.james.mailbox.jpa.quota.model.MaxUserStorage;
 import org.apache.james.mailbox.jpa.user.model.JPASubscription;
@@ -51,6 +53,8 @@ public interface JPAMailboxFixture {
     List<Class<?>> QUOTA_PERSISTANCE_CLASSES = ImmutableList.<Class<?>>of(
         MaxDefaultMessageCount.class,
         MaxDefaultStorage.class,
+        MaxDomainStorage.class,
+        MaxDomainMessageCount.class,
         MaxUserMessageCount.class,
         MaxUserStorage.class,
         JpaCurrentQuota.class
@@ -69,6 +73,8 @@ public interface JPAMailboxFixture {
         "JAMES_MAX_DEFAULT_STORAGE",
         "JAMES_MAX_USER_MESSAGE_COUNT",
         "JAMES_MAX_USER_STORAGE",
+        "JAMES_MAX_DOMAIN_MESSAGE_COUNT",
+        "JAMES_MAX_DOMAIN_STORAGE",
         "JAMES_QUOTA_CURRENTQUOTA"
     );
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryPerUserMaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryPerUserMaxQuotaManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryPerUserMaxQuotaManager.java
index aff15b9..1026cd5 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryPerUserMaxQuotaManager.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryPerUserMaxQuotaManager.java
@@ -38,6 +38,9 @@ public class InMemoryPerUserMaxQuotaManager implements MaxQuotaManager {
     private Optional<QuotaCount> maxMessage = Optional.empty();
     private Optional<QuotaSize> maxStorage = Optional.empty();
 
+    private final Map<String, QuotaCount> domainMaxMessage = new ConcurrentHashMap<>();
+    private final Map<String, QuotaSize> domainMaxStorage = new ConcurrentHashMap<>();
+
     private final Map<String, QuotaSize> userMaxStorage = new ConcurrentHashMap<>();
     private final Map<String, QuotaCount> userMaxMessage = new ConcurrentHashMap<>();
 
@@ -47,8 +50,23 @@ public class InMemoryPerUserMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
-    public void setDefaultMaxMessage(QuotaCount maxMessage) throws MailboxException {
-        this.maxMessage = Optional.of(maxMessage);
+    public void setDomainMaxMessage(String domain, QuotaCount count) {
+        domainMaxMessage.put(domain, count);
+    }
+
+    @Override
+    public void setDomainMaxStorage(String domain, QuotaSize size) {
+        domainMaxStorage.put(domain, size);
+    }
+
+    @Override
+    public void removeDomainMaxMessage(String domain) throws MailboxException {
+        domainMaxMessage.remove(domain);
+    }
+
+    @Override
+    public void removeDomainMaxStorage(String domain) {
+        domainMaxStorage.remove(domain);
     }
 
     @Override
@@ -88,6 +106,21 @@ public class InMemoryPerUserMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public void setDefaultMaxMessage(QuotaCount maxMessage) throws MailboxException {
+        this.maxMessage = Optional.of(maxMessage);
+    }
+
+    @Override
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        return Optional.ofNullable(domainMaxMessage.get(domain));
+    }
+
+    @Override
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        return Optional.ofNullable(domainMaxStorage.get(domain));
+    }
+
+    @Override
     public void setMaxStorage(QuotaRoot user, QuotaSize maxStorageQuota) {
         userMaxStorage.put(user.getValue(), maxStorageQuota);
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedMaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedMaxQuotaManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedMaxQuotaManager.java
index e4ada35..6190820 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedMaxQuotaManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedMaxQuotaManager.java
@@ -44,6 +44,16 @@ public class FixedMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public void setDomainMaxMessage(String domain, QuotaCount count) throws MailboxException {
+        throw new UnsupportedOperationException("Can not modify domain specific upper limit for FixedMaxQuotaManager");
+    }
+
+    @Override
+    public void setDomainMaxStorage(String domain, QuotaSize size) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("Can not modify domain specific upper limit for FixedMaxQuotaManager");
+    }
+
+    @Override
     public void setDefaultMaxStorage(QuotaSize defaultMaxStorage) {
         maxStorage = Optional.of(defaultMaxStorage);
     }
@@ -88,6 +98,30 @@ public class FixedMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        return Optional.empty();
+    }
+
+    public Optional<QuotaCount> getMaxMessage() {
+        return Optional.empty();
+    }
+
+    @Override
+    public void removeDomainMaxMessage(String domain) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("Can not modify domain specific upper limit for FixedMaxQuotaManager");
+    }
+
+    @Override
+    public void removeDomainMaxStorage(String domain) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("Can not modify domain specific upper limit for FixedMaxQuotaManager");
+    }
+
+    @Override
     public Optional<QuotaSize> getDefaultMaxStorage() {
         return maxStorage;
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/NoMaxQuotaManager.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/NoMaxQuotaManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/NoMaxQuotaManager.java
index 9e06bc3..f30b86b 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/NoMaxQuotaManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/NoMaxQuotaManager.java
@@ -59,6 +59,26 @@ public class NoMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public void setDomainMaxMessage(String domain, QuotaCount count) throws MailboxException {
+        throw new MailboxException("Operation is not supported");
+    }
+
+    @Override
+    public void setDomainMaxStorage(String domain, QuotaSize size) throws MailboxException {
+        throw new MailboxException("Operation is not supported");
+    }
+
+    @Override
+    public void removeDomainMaxMessage(String domain) throws MailboxException {
+        throw new MailboxException("Operation is not supported");
+    }
+
+    @Override
+    public void removeDomainMaxStorage(String domain) throws MailboxException {
+        throw new MailboxException("Operation is not supported");
+    }
+
+    @Override
     public void setDefaultMaxStorage(QuotaSize defaultMaxStorage) throws MailboxException {
         throw new MailboxException("Operation is not supported");
     }
@@ -99,6 +119,16 @@ public class NoMaxQuotaManager implements MaxQuotaManager {
     }
 
     @Override
+    public Optional<QuotaCount> getDomainMaxMessage(String domain) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<QuotaSize> getDomainMaxStorage(String domain) {
+        return Optional.empty();
+    }
+
+    @Override
     public Optional<QuotaSize> getDefaultMaxStorage() {
         return Optional.empty();
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
index cef25e2..b2f152c 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
@@ -27,12 +27,14 @@ import org.apache.james.mailbox.quota.MaxQuotaManager;
 import org.apache.james.mailbox.quota.QuotaCount;
 import org.apache.james.mailbox.quota.QuotaSize;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public abstract class GenericMaxQuotaManagerTest {
 
     private QuotaRoot quotaRoot;
     private MaxQuotaManager maxQuotaManager;
+    private String domain;
 
     protected abstract MaxQuotaManager provideMaxQuotaManager();
 
@@ -40,6 +42,7 @@ public abstract class GenericMaxQuotaManagerTest {
     public void setUp() {
         maxQuotaManager = provideMaxQuotaManager();
         quotaRoot = QuotaRoot.quotaRoot("benwa");
+        domain = "domain";
     }
 
     @Test
@@ -52,6 +55,14 @@ public abstract class GenericMaxQuotaManagerTest {
         assertThat(maxQuotaManager.getMaxStorage(quotaRoot)).isEmpty();
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void getMaxMessageShouldReturnDomainWhenNoValue() throws Exception {
+        maxQuotaManager.setDefaultMaxMessage(QuotaCount.count(36));
+        maxQuotaManager.setDomainMaxMessage(domain, QuotaCount.count(23));
+        assertThat(maxQuotaManager.getMaxMessage(quotaRoot)).contains(QuotaCount.count(23));
+    }
+
     @Test
     public void getMaxMessageShouldReturnDefaultWhenNoValue() throws Exception {
         maxQuotaManager.setDefaultMaxMessage(QuotaCount.count(36));
@@ -64,6 +75,14 @@ public abstract class GenericMaxQuotaManagerTest {
         assertThat(maxQuotaManager.getMaxStorage(quotaRoot)).contains(QuotaSize.size(36));
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void getMaxStorageShouldReturnDomainWhenNoValue() throws Exception {
+        maxQuotaManager.setDefaultMaxStorage(QuotaSize.size(234));
+        maxQuotaManager.setDomainMaxStorage(domain, QuotaSize.size(111));
+        assertThat(maxQuotaManager.getMaxStorage(quotaRoot)).contains(QuotaSize.size(111));
+    }
+
     @Test
     public void getMaxMessageShouldReturnProvidedValue() throws Exception {
         maxQuotaManager.setMaxMessage(quotaRoot, QuotaCount.count(36));
@@ -122,6 +141,14 @@ public abstract class GenericMaxQuotaManagerTest {
             .containsEntry(Quota.Scope.Global, QuotaCount.count(123));
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void listMaxMessagesDetailsShouldReturnDomainValueWhenDefined() throws Exception {
+        maxQuotaManager.setDomainMaxMessage(domain, QuotaCount.count(123));
+        assertThat(maxQuotaManager.listMaxMessagesDetails(quotaRoot))
+            .hasSize(1)
+            .containsEntry(Quota.Scope.Domain, QuotaCount.count(123));
+    }
 
     @Test
     public void listMaxMessagesDetailsShouldReturnUserValueWhenDefined() throws Exception {
@@ -132,7 +159,7 @@ public abstract class GenericMaxQuotaManagerTest {
     }
 
     @Test
-    public void listMaxMessagesDetailsShouldReturnBothValuesWhenDefined() throws Exception {
+    public void listMaxMessagesDetailsShouldReturnBothValuesWhenGlobalAndUserDefined() throws Exception {
         maxQuotaManager.setDefaultMaxMessage(QuotaCount.count(1234));
         maxQuotaManager.setMaxMessage(quotaRoot, QuotaCount.count(123));
         assertThat(maxQuotaManager.listMaxMessagesDetails(quotaRoot))
@@ -141,6 +168,18 @@ public abstract class GenericMaxQuotaManagerTest {
             .containsEntry(Quota.Scope.User, QuotaCount.count(123));
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void listMaxMessagesDetailsShouldReturnAllValuesWhenDefined() throws Exception {
+        maxQuotaManager.setDefaultMaxMessage(QuotaCount.count(1234));
+        maxQuotaManager.setDomainMaxMessage(domain, QuotaCount.count(333));
+        maxQuotaManager.setMaxMessage(quotaRoot, QuotaCount.count(123));
+        assertThat(maxQuotaManager.listMaxMessagesDetails(quotaRoot))
+            .hasSize(3)
+            .containsEntry(Quota.Scope.Global, QuotaCount.count(1234))
+            .containsEntry(Quota.Scope.Domain, QuotaCount.count(333))
+            .containsEntry(Quota.Scope.User, QuotaCount.count(123));
+    }
 
     @Test
     public void listMaxStorageDetailsShouldReturnGlobalValueWhenDefined() throws Exception {
@@ -150,6 +189,14 @@ public abstract class GenericMaxQuotaManagerTest {
             .containsEntry(Quota.Scope.Global, QuotaSize.size(1111));
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void listMaxStorageDetailsShouldReturnDomainValueWhenDefined() throws Exception {
+        maxQuotaManager.setDomainMaxStorage(domain, QuotaSize.size(1111));
+        assertThat(maxQuotaManager.listMaxStorageDetails(quotaRoot))
+            .hasSize(1)
+            .containsEntry(Quota.Scope.Domain, QuotaSize.size(1111));
+    }
 
     @Test
     public void listMaxStorageDetailsShouldReturnUserValueWhenDefined() throws Exception {
@@ -169,4 +216,53 @@ public abstract class GenericMaxQuotaManagerTest {
             .containsEntry(Quota.Scope.User, QuotaSize.size(4444));
     }
 
+    @Ignore("how can we link domain and quotaRoot ?")
+    @Test
+    public void listMaxStorageDetailsShouldReturnAllValuesWhenDefined() throws Exception {
+        maxQuotaManager.setDefaultMaxStorage(QuotaSize.size(3333));
+        maxQuotaManager.setDomainMaxStorage(domain, QuotaSize.size(2222));
+        maxQuotaManager.setMaxStorage(quotaRoot, QuotaSize.size(4444));
+        assertThat(maxQuotaManager.listMaxStorageDetails(quotaRoot))
+            .hasSize(3)
+            .containsEntry(Quota.Scope.Global, QuotaSize.size(3333))
+            .containsEntry(Quota.Scope.Domain, QuotaSize.size(2222))
+            .containsEntry(Quota.Scope.User, QuotaSize.size(4444));
+    }
+
+    @Test
+    public void getDomainMaxMessageShouldReturnEmptyWhenNoDefaultValue() {
+        assertThat(maxQuotaManager.getDomainMaxMessage(domain)).isEmpty();
+    }
+
+    @Test
+    public void getDomainMaxStorageShouldReturnEmptyWhenNoDefaultValue() {
+        assertThat(maxQuotaManager.getDomainMaxStorage(domain)).isEmpty();
+    }
+
+    @Test
+    public void getDomainMaxMessageShouldReturnProvidedValue() throws Exception {
+        maxQuotaManager.setDomainMaxMessage(domain, QuotaCount.count(36));
+        assertThat(maxQuotaManager.getDomainMaxMessage(domain)).contains(QuotaCount.count(36));
+    }
+
+    @Test
+    public void getDomainMaxStorageShouldReturnProvidedValue() throws Exception {
+        maxQuotaManager.setDomainMaxStorage(domain, QuotaSize.size(36));
+        assertThat(maxQuotaManager.getDomainMaxStorage(domain)).contains(QuotaSize.size(36));
+    }
+
+    @Test
+    public void deleteDomainMaxStorageShouldRemoveCurrentValue() throws Exception {
+        maxQuotaManager.setDomainMaxStorage(domain, QuotaSize.size(36));
+        maxQuotaManager.removeDomainMaxStorage(domain);
+        assertThat(maxQuotaManager.getDomainMaxStorage(domain)).isEmpty();
+    }
+
+    @Test
+    public void deleteDomainMaxMessageShouldRemoveCurrentValue() throws Exception {
+        maxQuotaManager.setDomainMaxMessage(domain, QuotaCount.count(36));
+        maxQuotaManager.removeDomainMaxMessage(domain);
+        assertThat(maxQuotaManager.getDomainMaxMessage(domain)).isEmpty();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
----------------------------------------------------------------------
diff --git a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
index c27c9ca..b19d81e 100644
--- a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
+++ b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
@@ -49,6 +49,7 @@ import org.apache.james.mailbox.cassandra.modules.CassandraSubscriptionModule;
 import org.apache.james.mailbox.cassandra.modules.CassandraUidModule;
 import org.apache.james.mailbox.cassandra.quota.CassandraCurrentQuotaManager;
 import org.apache.james.mailbox.cassandra.quota.CassandraDefaultMaxQuotaDao;
+import org.apache.james.mailbox.cassandra.quota.CassandraPerDomainMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaManager;
 import org.apache.james.mailbox.exception.MailboxException;
@@ -129,6 +130,7 @@ public class CassandraHostSystem extends JamesImapHostSystem {
 
         perUserMaxQuotaManager = new CassandraPerUserMaxQuotaManager(
             new CassandraPerUserMaxQuotaDao(session),
+            new CassandraPerDomainMaxQuotaDao(cassandra.getConf()),
             new CassandraDefaultMaxQuotaDao(session));
 
         CassandraCurrentQuotaManager currentQuotaManager = new CassandraCurrentQuotaManager(session);

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraQuotaModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraQuotaModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraQuotaModule.java
index 6d3a04b..9ef5286 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraQuotaModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraQuotaModule.java
@@ -22,6 +22,7 @@ package org.apache.james.modules.mailbox;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.mailbox.cassandra.quota.CassandraCurrentQuotaManager;
 import org.apache.james.mailbox.cassandra.quota.CassandraDefaultMaxQuotaDao;
+import org.apache.james.mailbox.cassandra.quota.CassandraPerDomainMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaDao;
 import org.apache.james.mailbox.cassandra.quota.CassandraPerUserMaxQuotaManager;
 import org.apache.james.mailbox.quota.CurrentQuotaManager;
@@ -43,6 +44,7 @@ public class CassandraQuotaModule extends AbstractModule {
     protected void configure() {
         bind(CassandraCurrentQuotaManager.class).in(Scopes.SINGLETON);
         bind(CassandraDefaultMaxQuotaDao.class).in(Scopes.SINGLETON);
+        bind(CassandraPerDomainMaxQuotaDao.class).in(Scopes.SINGLETON);
         bind(CassandraPerUserMaxQuotaDao.class).in(Scopes.SINGLETON);
         bind(CassandraPerUserMaxQuotaManager.class).in(Scopes.SINGLETON);
         bind(DefaultUserQuotaRootResolver.class).in(Scopes.SINGLETON);

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaRoutes.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaRoutes.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaRoutes.java
new file mode 100644
index 0000000..f8c0ace
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaRoutes.java
@@ -0,0 +1,297 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.dto.QuotaDTO;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.ErrorResponder.ErrorType;
+import org.apache.james.webadmin.utils.JsonExtractException;
+import org.apache.james.webadmin.utils.JsonExtractor;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.utils.JsonTransformerModule;
+import org.apache.james.webadmin.validation.Quotas;
+import org.eclipse.jetty.http.HttpStatus;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import spark.Request;
+import spark.Service;
+
+@Api(tags = "DomainQuota")
+@Path(DomainQuotaRoutes.QUOTA_ENDPOINT)
+@Produces("application/json")
+public class DomainQuotaRoutes implements Routes {
+
+    private static final String DOMAIN = "domain";
+    static final String QUOTA_ENDPOINT = "/quota/domains/:" + DOMAIN;
+    private static final String COUNT_ENDPOINT = QUOTA_ENDPOINT + "/count";
+    private static final String SIZE_ENDPOINT = QUOTA_ENDPOINT + "/size";
+
+    private final DomainList domainList;
+    private final DomainQuotaService domainQuotaService;
+    private final JsonTransformer jsonTransformer;
+    private final JsonExtractor<QuotaDTO> jsonExtractor;
+    private Service service;
+
+    @Inject
+    public DomainQuotaRoutes(DomainList domainList, DomainQuotaService domainQuotaService, JsonTransformer jsonTransformer, Set<JsonTransformerModule> modules) {
+        this.domainList = domainList;
+        this.domainQuotaService = domainQuotaService;
+        this.jsonTransformer = jsonTransformer;
+        this.jsonExtractor = new JsonExtractor<>(QuotaDTO.class, modules.stream().map(JsonTransformerModule::asJacksonModule).collect(Collectors.toList()));
+    }
+
+    @Override
+    public void define(Service service) {
+        this.service = service;
+
+        defineGetQuotaCount();
+        defineDeleteQuotaCount();
+        defineUpdateQuotaCount();
+
+        defineGetQuotaSize();
+        defineDeleteQuotaSize();
+        defineUpdateQuotaSize();
+
+        defineGetQuota();
+        defineUpdateQuota();
+    }
+
+    @PUT
+    @ApiOperation(value = "Updating count and size at the same time")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "org.apache.james.webadmin.dto.QuotaDTO", paramType = "body")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK. The value has been updated."),
+            @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "The body is not a positive integer or not unlimited value (-1)."),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.CONFLICT_409, message = "The requested restriction can't be enforced right now."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineUpdateQuota() {
+        service.put(QUOTA_ENDPOINT, ((request, response) -> {
+            String domain = checkDomainExist(request);
+            QuotaDTO quotaDTO = parseQuotaDTO(request);
+            domainQuotaService.defineQuota(domain, quotaDTO);
+            response.status(HttpStatus.NO_CONTENT_204);
+            return response;
+        }));
+    }
+
+    @GET
+    @ApiOperation(
+        value = "Reading count and size at the same time",
+        notes = "If there is no limitation for count and/or size, the returned value will be -1"
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.OK_200, message = "OK", response = QuotaDTO.class),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineGetQuota() {
+        service.get(QUOTA_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            return domainQuotaService.getQuota(domain);
+        }, jsonTransformer);
+    }
+
+    @DELETE
+    @Path("/size")
+    @ApiOperation(value = "Removing per domain mail size limitation by updating to unlimited value")
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "The value is updated to unlimited value."),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineDeleteQuotaSize() {
+        service.delete(SIZE_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            domainQuotaService.remoteMaxQuotaSize(domain);
+            response.status(HttpStatus.NO_CONTENT_204);
+            return response;
+        });
+    }
+
+    @PUT
+    @Path("/size")
+    @ApiOperation(value = "Updating per domain mail size limitation")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "integer", paramType = "body")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK. The value has been updated."),
+            @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "The body is not a positive integer."),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.CONFLICT_409, message = "The requested restriction can't be enforced right now."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineUpdateQuotaSize() {
+        service.put(SIZE_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            QuotaSize quotaSize = Quotas.quotaSize(request.body());
+            domainQuotaService.setMaxSizeQuota(domain, quotaSize);
+            response.status(HttpStatus.NO_CONTENT_204);
+            return response;
+        });
+    }
+
+    @GET
+    @Path("/size")
+    @ApiOperation(value = "Reading per domain mail size limitation")
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.OK_200, message = "OK", response = Long.class),
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "No value defined"),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineGetQuotaSize() {
+        service.get(SIZE_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            Optional<QuotaSize> maxSizeQuota = domainQuotaService.getMaxSizeQuota(domain);
+            if (maxSizeQuota.isPresent()) {
+                return maxSizeQuota;
+            }
+            response.status(HttpStatus.NO_CONTENT_204);
+            return null;
+        }, jsonTransformer);
+    }
+
+    @DELETE
+    @Path("/count")
+    @ApiOperation(value = "Removing per domain mail count limitation by updating to unlimited value")
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "The value is updated to unlimited value."),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineDeleteQuotaCount() {
+        service.delete(COUNT_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            domainQuotaService.remoteMaxQuotaCount(domain);
+            response.status(HttpStatus.NO_CONTENT_204);
+            return response;
+        });
+    }
+
+    @PUT
+    @Path("/count")
+    @ApiOperation(value = "Updating per domain mail count limitation")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "integer", paramType = "body")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK. The value has been updated."),
+            @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "The body is not a positive integer."),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.CONFLICT_409, message = "The requested restriction can't be enforced right now."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineUpdateQuotaCount() {
+        service.put(COUNT_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            QuotaCount quotaCount = Quotas.quotaCount(request.body());
+            domainQuotaService.setMaxCountQuota(domain, quotaCount);
+            response.status(HttpStatus.NO_CONTENT_204);
+            return response;
+        });
+    }
+
+    @GET
+    @Path("/count")
+    @ApiOperation(value = "Reading per domain mail count limitation")
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.OK_200, message = "OK", response = Long.class),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The requested rdomain can not be found."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineGetQuotaCount() {
+        service.get(COUNT_ENDPOINT, (request, response) -> {
+            String domain = checkDomainExist(request);
+            Optional<QuotaCount> maxCountQuota = domainQuotaService.getMaxCountQuota(domain);
+            if (maxCountQuota.isPresent()) {
+                return maxCountQuota;
+            }
+            response.status(HttpStatus.NO_CONTENT_204);
+            return null;
+        }, jsonTransformer);
+    }
+
+    private String checkDomainExist(Request request) {
+        String domain = request.params(DOMAIN);
+        try {
+            if (!domainList.containsDomain(domain)) {
+                throw ErrorResponder.builder()
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .type(ErrorType.NOT_FOUND)
+                    .message("Domain not found")
+                    .haltError();
+            }
+        } catch (DomainListException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorType.NOT_FOUND)
+                .cause(e)
+                .haltError();
+        }
+        return domain;
+    }
+
+    private QuotaDTO parseQuotaDTO(Request request) {
+        try {
+            return jsonExtractor.parse(request.body());
+        } catch (IllegalArgumentException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorType.INVALID_ARGUMENT)
+                .message("Quota should be positive or unlimited (-1)")
+                .cause(e)
+                .haltError();
+        } catch (JsonExtractException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorType.INVALID_ARGUMENT)
+                .message("Malformed JSON input")
+                .cause(e)
+                .haltError();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaService.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaService.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaService.java
new file mode 100644
index 0000000..35a10ff
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/DomainQuotaService.java
@@ -0,0 +1,81 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.quota.MaxQuotaManager;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+import org.apache.james.webadmin.dto.QuotaDTO;
+
+import com.github.fge.lambdas.Throwing;
+
+public class DomainQuotaService {
+
+    private final MaxQuotaManager maxQuotaManager;
+
+    @Inject
+    public DomainQuotaService(MaxQuotaManager maxQuotaManager) {
+        this.maxQuotaManager = maxQuotaManager;
+    }
+
+    public Optional<QuotaCount> getMaxCountQuota(String domain) {
+        return maxQuotaManager.getDomainMaxMessage(domain);
+    }
+
+    public void setMaxCountQuota(String domain, QuotaCount quotaCount) throws MailboxException {
+        maxQuotaManager.setDomainMaxMessage(domain, quotaCount);
+    }
+
+    public void remoteMaxQuotaCount(String domain) throws MailboxException {
+        maxQuotaManager.removeDomainMaxMessage(domain);
+    }
+
+    public Optional<QuotaSize> getMaxSizeQuota(String domain) {
+        return maxQuotaManager.getDomainMaxStorage(domain);
+    }
+
+    public void setMaxSizeQuota(String domain, QuotaSize quotaSize) throws MailboxException {
+        maxQuotaManager.setDomainMaxStorage(domain, quotaSize);
+    }
+
+    public void remoteMaxQuotaSize(String domain) throws MailboxException {
+        maxQuotaManager.removeDomainMaxStorage(domain);
+    }
+
+    public QuotaDTO getQuota(String domain) {
+        return QuotaDTO
+            .builder()
+            .count(maxQuotaManager.getDomainMaxMessage(domain))
+            .size(maxQuotaManager.getDomainMaxStorage(domain))
+            .build();
+    }
+
+    public void defineQuota(String domain, QuotaDTO quota) {
+        quota.getCount()
+            .ifPresent(Throwing.consumer(count -> maxQuotaManager.setDomainMaxMessage(domain, count)));
+        quota.getSize()
+            .ifPresent(Throwing.consumer(size -> maxQuotaManager.setDomainMaxStorage(domain, size)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/5191b5fa/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/DomainQuotaRoutesTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/DomainQuotaRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/DomainQuotaRoutesTest.java
new file mode 100644
index 0000000..9f9cab7
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/DomainQuotaRoutesTest.java
@@ -0,0 +1,430 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.RestAssured.when;
+import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.mailbox.inmemory.quota.InMemoryPerUserMaxQuotaManager;
+import org.apache.james.mailbox.quota.QuotaCount;
+import org.apache.james.mailbox.quota.QuotaSize;
+import org.apache.james.metrics.api.NoopMetricFactory;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.jackson.QuotaModule;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.path.json.JsonPath;
+
+public class DomainQuotaRoutesTest {
+
+    private static final String QUOTA_DOMAINS = "/quota/domains";
+    private static final String PERDU_COM = "perdu.com";
+    private static final String TROUVÉ_COM = "trouvé.com";
+    private static final String COUNT = "count";
+    private static final String SIZE = "size";
+    private WebAdminServer webAdminServer;
+    private InMemoryPerUserMaxQuotaManager maxQuotaManager;
+
+    @Before
+    public void setUp() throws Exception {
+        maxQuotaManager = new InMemoryPerUserMaxQuotaManager();
+        MemoryDomainList memoryDomainList = new MemoryDomainList(new InMemoryDNSService());
+        memoryDomainList.setAutoDetect(false);
+        memoryDomainList.addDomain(TROUVÉ_COM);
+        MemoryDomainList domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.addDomain(TROUVÉ_COM);
+        DomainQuotaService domainQuotaService = new DomainQuotaService(maxQuotaManager);
+        QuotaModule quotaModule = new QuotaModule();
+        DomainQuotaRoutes domainQuotaRoutes = new DomainQuotaRoutes(domainList, domainQuotaService, new JsonTransformer(quotaModule), ImmutableSet.of(quotaModule));
+        webAdminServer = WebAdminUtils.createWebAdminServer(
+            new NoopMetricFactory(),
+            domainQuotaRoutes);
+        webAdminServer.configure(NO_CONFIGURATION);
+        webAdminServer.await();
+
+        RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+            .build();
+        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+    }
+
+    @After
+    public void stop() {
+        webAdminServer.destroy();
+    }
+
+    @Test
+    public void getCountShouldReturnNotFoundWhenDomainDoesntExist() {
+        when()
+            .get(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void getCountShouldReturnNoContentByDefault() {
+        given()
+            .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+    }
+
+    @Test
+    public void getCountShouldReturnStoredValue() {
+        int value = 42;
+        maxQuotaManager.setDomainMaxMessage(TROUVÉ_COM, QuotaCount.count(value));
+
+        Long actual =
+            given()
+                .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+            .then()
+                .statusCode(HttpStatus.OK_200)
+                .contentType(ContentType.JSON)
+                .extract()
+                .as(Long.class);
+
+        assertThat(actual).isEqualTo(value);
+    }
+
+    @Test
+    public void putCountShouldReturnNotFoundWhenDomainDoesntExist() {
+        given()
+            .body("123")
+        .when()
+            .put(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void putCountShouldRejectInvalid() {
+        Map<String, Object> errors = given()
+            .body("invalid")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .contentType(ContentType.JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+            .containsEntry("type", "InvalidArgument")
+            .containsEntry("message", "Invalid quota. Need to be an integer value greater or equal to -1")
+            .containsEntry("cause", "For input string: \"invalid\"");
+    }
+
+    @Test
+    public void putCountShouldSetToInfiniteWhenMinusOne() {
+        given()
+            .body("-1")
+        .when()
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxMessage(TROUVÉ_COM)).contains(QuotaCount.unlimited());
+    }
+
+    @Test
+    public void putCountShouldRejectNegativeOtherThanMinusOne() {
+        Map<String, Object> errors = given()
+            .body("-2")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .contentType(ContentType.JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+            .containsEntry("type", "InvalidArgument")
+            .containsEntry("message", "Invalid quota. Need to be an integer value greater or equal to -1");
+    }
+
+    @Test
+    public void putCountShouldAcceptValidValue() {
+        given()
+            .body("42")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxMessage(TROUVÉ_COM)).contains(QuotaCount.count(42));
+    }
+
+    @Test
+    public void deleteCountShouldReturnNotFoundWhenDomainDoesntExist() {
+        when()
+            .delete(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void deleteCountShouldSetQuotaToEmpty() {
+        maxQuotaManager.setDomainMaxMessage(TROUVÉ_COM, QuotaCount.count(42));
+
+        given()
+            .delete(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + COUNT)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxMessage(TROUVÉ_COM)).isEmpty();
+    }
+
+    @Test
+    public void getSizeShouldReturnNotFoundWhenDomainDoesntExist() {
+            when()
+                .get(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + SIZE)
+            .then()
+                .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void getSizeShouldReturnNoContentByDefault() {
+        when()
+            .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+    }
+
+    @Test
+    public void getSizeShouldReturnStoredValue() {
+        long value = 42;
+        maxQuotaManager.setDomainMaxStorage(TROUVÉ_COM, QuotaSize.size(value));
+
+
+        long quota =
+            given()
+                .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+            .then()
+                .statusCode(HttpStatus.OK_200)
+                .contentType(ContentType.JSON)
+                .extract()
+                .as(Long.class);
+
+        assertThat(quota).isEqualTo(value);
+    }
+
+    @Test
+    public void putSizeShouldRejectInvalid() {
+        Map<String, Object> errors = given()
+            .body("invalid")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .contentType(ContentType.JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+            .containsEntry("type", "InvalidArgument")
+            .containsEntry("message", "Invalid quota. Need to be an integer value greater or equal to -1")
+            .containsEntry("cause", "For input string: \"invalid\"");
+    }
+
+    @Test
+    public void putSizeShouldReturnNotFoundWhenDomainDoesntExist() {
+        given()
+            .body("123")
+        .when()
+            .put(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void putSizeShouldSetToInfiniteWhenMinusOne() {
+        given()
+            .body("-1")
+        .when()
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxStorage(TROUVÉ_COM)).contains(QuotaSize.unlimited());
+    }
+
+    @Test
+    public void putSizeShouldRejectNegativeOtherThanMinusOne() {
+        Map<String, Object> errors = given()
+            .body("-2")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .contentType(ContentType.JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+            .containsEntry("type", "InvalidArgument")
+            .containsEntry("message", "Invalid quota. Need to be an integer value greater or equal to -1");
+    }
+
+    @Test
+    public void putSizeShouldAcceptValidValue() {
+        given()
+            .body("42")
+        .when()
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxStorage(TROUVÉ_COM)).contains(QuotaSize.size(42));
+    }
+
+    @Test
+    public void deleteSizeShouldReturnNotFoundWhenDomainDoesntExist() {
+        when()
+            .delete(QUOTA_DOMAINS + "/" + PERDU_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void deleteSizeShouldSetQuotaToEmpty() {
+        maxQuotaManager.setDomainMaxStorage(TROUVÉ_COM, QuotaSize.size(42));
+
+        given()
+            .delete(QUOTA_DOMAINS + "/" + TROUVÉ_COM + "/" + SIZE)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxStorage(TROUVÉ_COM)).isEmpty();
+    }
+
+    @Test
+    public void getQuotaShouldReturnNotFoundWhenDomainDoesntExist() {
+        when()
+            .get(QUOTA_DOMAINS + "/" + PERDU_COM)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void getQuotaShouldReturnBothEmptyWhenDefaultValues() {
+        JsonPath jsonPath =
+            given()
+                .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM)
+            .then()
+                .statusCode(HttpStatus.OK_200)
+                .contentType(ContentType.JSON)
+                .extract()
+                .jsonPath();
+
+        assertThat(jsonPath.getObject(SIZE, Long.class)).isNull();
+        assertThat(jsonPath.getObject(COUNT, Long.class)).isNull();
+    }
+
+    @Test
+    public void getQuotaShouldReturnSizeWhenNoCount() {
+        int maxStorage = 42;
+        maxQuotaManager.setDomainMaxStorage(TROUVÉ_COM, QuotaSize.size(maxStorage));
+
+        JsonPath jsonPath =
+            given()
+                .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM)
+            .then()
+                .statusCode(HttpStatus.OK_200)
+                .contentType(ContentType.JSON)
+                .extract()
+                .jsonPath();
+
+        assertThat(jsonPath.getLong(SIZE)).isEqualTo(maxStorage);
+        assertThat(jsonPath.getObject(COUNT, Long.class)).isNull();
+    }
+
+    @Test
+    public void getQuotaShouldReturnBothWhenNoSize() {
+        int maxMessage = 42;
+        maxQuotaManager.setDomainMaxMessage(TROUVÉ_COM, QuotaCount.count(maxMessage));
+
+
+        JsonPath jsonPath =
+            given()
+                .get(QUOTA_DOMAINS + "/" + TROUVÉ_COM)
+            .then()
+                .statusCode(HttpStatus.OK_200)
+                .contentType(ContentType.JSON)
+                .extract()
+                .jsonPath();
+
+        assertThat(jsonPath.getObject(SIZE, Long.class)).isNull();
+        assertThat(jsonPath.getLong(COUNT)).isEqualTo(maxMessage);
+    }
+
+    @Test
+    public void putQuotaShouldReturnNotFoundWhenDomainDoesntExist() {
+        when()
+            .put(QUOTA_DOMAINS + "/" + PERDU_COM)
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404);
+    }
+
+    @Test
+    public void putQuotaShouldUpdateBothQuota() {
+        given()
+            .body("{\"count\":52,\"size\":42}")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxMessage(TROUVÉ_COM)).contains(QuotaCount.count(52));
+        assertThat(maxQuotaManager.getDomainMaxStorage(TROUVÉ_COM)).contains(QuotaSize.size(42));
+    }
+
+    @Test
+    public void putQuotaShouldBeAbleToRemoveBothQuota() {
+        given()
+            .body("{\"count\":null,\"count\":null}")
+            .put(QUOTA_DOMAINS + "/" + TROUVÉ_COM)
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        assertThat(maxQuotaManager.getDomainMaxStorage(TROUVÉ_COM)).isEmpty();
+        assertThat(maxQuotaManager.getDomainMaxMessage(TROUVÉ_COM)).isEmpty();
+    }
+
+}


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