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 <[email protected]> Authored: Mon Mar 12 17:24:51 2018 +0100 Committer: benwa <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
