This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch postgresql in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 0628f5fdc8e05fdf80bcd1498e74f1301720a4af Author: Quan Tran <[email protected]> AuthorDate: Mon Nov 27 18:02:53 2023 +0700 JAMES-2586 Implement SieveQuotaRepository backed by Postgres --- .../src/main/resources/META-INF/persistence.xml | 3 +- server/data/data-postgres/pom.xml | 8 +- .../james/sieve/jpa/model/JPASieveQuota.java | 97 ------------ .../sieve/postgres/PostgresSieveQuotaDAO.java | 114 ++++++++++++++ .../PostgresSieveRepository.java} | 134 +++++++---------- .../{jpa => postgres}/model/JPASieveScript.java | 2 +- .../sieve/postgres/PostgresSieveQuotaDAOTest.java | 163 +++++++++++++++++++++ .../PostgresSieveRepositoryTest.java} | 23 ++- .../src/test/resources/persistence.xml | 4 +- 9 files changed, 351 insertions(+), 197 deletions(-) diff --git a/server/apps/postgres-app/src/main/resources/META-INF/persistence.xml b/server/apps/postgres-app/src/main/resources/META-INF/persistence.xml index 5d55f9b767..9573f6e5f6 100644 --- a/server/apps/postgres-app/src/main/resources/META-INF/persistence.xml +++ b/server/apps/postgres-app/src/main/resources/META-INF/persistence.xml @@ -34,8 +34,7 @@ <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class> <class>org.apache.james.mailrepository.jpa.model.JPAMail</class> <class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class> - <class>org.apache.james.sieve.jpa.model.JPASieveQuota</class> - <class>org.apache.james.sieve.jpa.model.JPASieveScript</class> + <class>org.apache.james.sieve.postgres.model.JPASieveScript</class> <class>org.apache.james.mailbox.postgres.quota.model.MaxDomainMessageCount</class> <class>org.apache.james.mailbox.postgres.quota.model.MaxDomainStorage</class> diff --git a/server/data/data-postgres/pom.xml b/server/data/data-postgres/pom.xml index 82e0bec73b..223cf0a802 100644 --- a/server/data/data-postgres/pom.xml +++ b/server/data/data-postgres/pom.xml @@ -155,9 +155,7 @@ <artifactId>openjpa-maven-plugin</artifactId> <version>${apache.openjpa.version}</version> <configuration> - <includes>org/apache/james/sieve/jpa/model/JPASieveQuota.class, - org/apache/james/sieve/jpa/model/JPASieveScript.class, - org/apache/james/user/jpa/model/JPAUser.class, + <includes>org/apache/james/sieve/postgres/model/JPASieveScript.class, org/apache/james/rrt/jpa/model/JPARecipientRewrite.class, org/apache/james/domainlist/jpa/model/JPADomain.class, org/apache/james/mailrepository/jpa/model/JPAUrl.class, @@ -171,9 +169,7 @@ </property> <property> <name>metaDataFactory</name> - <value>jpa(Types=org.apache.james.sieve.jpa.model.JPASieveQuota; - org.apache.james.sieve.jpa.model.JPASieveScript; - org.apache.james.user.jpa.model.JPAUser; + <value>jpa(Types=org.apache.james.sieve.postgres.model.JPASieveScript; org.apache.james.rrt.jpa.model.JPARecipientRewrite; org.apache.james.domainlist.jpa.model.JPADomain; org.apache.james.mailrepository.jpa.model.JPAUrl; diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java deleted file mode 100644 index 52485c12ec..0000000000 --- a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java +++ /dev/null @@ -1,97 +0,0 @@ -/**************************************************************** - * 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.sieve.jpa.model; - -import java.util.Objects; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.Table; - -import org.apache.james.core.quota.QuotaSizeLimit; - -import com.google.common.base.MoreObjects; - -@Entity(name = "JamesSieveQuota") -@Table(name = "JAMES_SIEVE_QUOTA") -@NamedQueries({ - @NamedQuery(name = "findByUsername", query = "SELECT sieveQuota FROM JamesSieveQuota sieveQuota WHERE sieveQuota.username=:username") -}) -public class JPASieveQuota { - - @Id - @Column(name = "USER_NAME", nullable = false, length = 100) - private String username; - - @Column(name = "SIZE", nullable = false) - private long size; - - /** - * @deprecated enhancement only - */ - @Deprecated - protected JPASieveQuota() { - } - - public JPASieveQuota(String username, long size) { - this.username = username; - this.size = size; - } - - public long getSize() { - return size; - } - - public void setSize(QuotaSizeLimit quotaSize) { - this.size = quotaSize.asLong(); - } - - public QuotaSizeLimit toQuotaSize() { - return QuotaSizeLimit.size(size); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JPASieveQuota that = (JPASieveQuota) o; - return Objects.equals(username, that.username); - } - - @Override - public int hashCode() { - return Objects.hash(username); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("username", username) - .add("size", size) - .toString(); - } -} diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAO.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAO.java new file mode 100644 index 0000000000..dff7d4ef71 --- /dev/null +++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAO.java @@ -0,0 +1,114 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.sieve.postgres; + +import static org.apache.james.core.quota.QuotaType.SIZE; + +import java.util.Optional; + +import org.apache.james.backends.postgres.quota.PostgresQuotaCurrentValueDAO; +import org.apache.james.backends.postgres.quota.PostgresQuotaLimitDAO; +import org.apache.james.core.Username; +import org.apache.james.core.quota.QuotaComponent; +import org.apache.james.core.quota.QuotaCurrentValue; +import org.apache.james.core.quota.QuotaLimit; +import org.apache.james.core.quota.QuotaScope; +import org.apache.james.core.quota.QuotaSizeLimit; + +import reactor.core.publisher.Mono; + +public class PostgresSieveQuotaDAO { + public static final QuotaComponent QUOTA_COMPONENT = QuotaComponent.of("SIEVE"); + public static final String GLOBAL = "GLOBAL"; + + private final PostgresQuotaCurrentValueDAO currentValueDao; + private final PostgresQuotaLimitDAO limitDao; + + public PostgresSieveQuotaDAO(PostgresQuotaCurrentValueDAO currentValueDao, PostgresQuotaLimitDAO limitDao) { + this.currentValueDao = currentValueDao; + this.limitDao = limitDao; + } + + public Mono<Long> spaceUsedBy(Username username) { + QuotaCurrentValue.Key quotaKey = asQuotaKey(username); + + return currentValueDao.getQuotaCurrentValue(quotaKey).map(QuotaCurrentValue::getCurrentValue) + .switchIfEmpty(Mono.just(0L)); + } + + private QuotaCurrentValue.Key asQuotaKey(Username username) { + return QuotaCurrentValue.Key.of( + QUOTA_COMPONENT, + username.asString(), + SIZE); + } + + public Mono<Void> updateSpaceUsed(Username username, long spaceUsed) { + QuotaCurrentValue.Key quotaKey = asQuotaKey(username); + + return currentValueDao.increase(quotaKey, spaceUsed); + } + + public Mono<Optional<QuotaSizeLimit>> getGlobalQuota() { + return limitDao.getQuotaLimit(QuotaLimit.QuotaLimitKey.of(QUOTA_COMPONENT, QuotaScope.GLOBAL, GLOBAL, SIZE)) + .map(v -> v.getQuotaLimit().map(QuotaSizeLimit::size)) + .switchIfEmpty(Mono.just(Optional.empty())); + } + + public Mono<Void> setGlobalQuota(QuotaSizeLimit quota) { + return limitDao.setQuotaLimit(QuotaLimit.builder() + .quotaComponent(QUOTA_COMPONENT) + .quotaScope(QuotaScope.GLOBAL) + .quotaType(SIZE) + .identifier(GLOBAL) + .quotaLimit(quota.asLong()) + .build()); + } + + public Mono<Void> removeGlobalQuota() { + return limitDao.deleteQuotaLimit(QuotaLimit.QuotaLimitKey.of(QUOTA_COMPONENT, QuotaScope.GLOBAL, GLOBAL, SIZE)); + } + + public Mono<Optional<QuotaSizeLimit>> getQuota(Username username) { + return limitDao.getQuotaLimits(QUOTA_COMPONENT, QuotaScope.USER, username.asString()) + .map(v -> v.getQuotaLimit().map(QuotaSizeLimit::size)) + .switchIfEmpty(Mono.just(Optional.empty())) + .single(); + } + + public Mono<Void> setQuota(Username username, QuotaSizeLimit quota) { + return limitDao.setQuotaLimit(QuotaLimit.builder() + .quotaComponent(QUOTA_COMPONENT) + .quotaScope(QuotaScope.USER) + .quotaType(SIZE) + .identifier(username.asString()) + .quotaLimit(quota.asLong()) + .build()); + } + + public Mono<Void> removeQuota(Username username) { + return limitDao.deleteQuotaLimit(QuotaLimit.QuotaLimitKey.of( + QUOTA_COMPONENT, QuotaScope.USER, username.asString(), SIZE)); + } + + public Mono<Void> resetSpaceUsed(Username username, long spaceUsed) { + return spaceUsedBy(username).flatMap(currentSpace -> currentValueDao.increase(asQuotaKey(username), spaceUsed - currentSpace)); + } +} diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java similarity index 73% rename from server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java rename to server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java index 53c96fc263..544ef43999 100644 --- a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java +++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.sieve.jpa; +package org.apache.james.sieve.postgres; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -39,8 +39,7 @@ import org.apache.james.backends.jpa.TransactionRunner; import org.apache.james.core.Username; import org.apache.james.core.quota.QuotaSizeLimit; import org.apache.james.core.quota.QuotaSizeUsage; -import org.apache.james.sieve.jpa.model.JPASieveQuota; -import org.apache.james.sieve.jpa.model.JPASieveScript; +import org.apache.james.sieve.postgres.model.JPASieveScript; import org.apache.james.sieverepository.api.ScriptContent; import org.apache.james.sieverepository.api.ScriptName; import org.apache.james.sieverepository.api.ScriptSummary; @@ -60,16 +59,17 @@ import com.google.common.collect.ImmutableList; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -public class JPASieveRepository implements SieveRepository { +public class PostgresSieveRepository implements SieveRepository { - private static final Logger LOGGER = LoggerFactory.getLogger(JPASieveRepository.class); - private static final String DEFAULT_SIEVE_QUOTA_USERNAME = "default.quota"; + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresSieveRepository.class); private final TransactionRunner transactionRunner; + private final PostgresSieveQuotaDAO postgresSieveQuotaDAO; @Inject - public JPASieveRepository(EntityManagerFactory entityManagerFactory) { + public PostgresSieveRepository(EntityManagerFactory entityManagerFactory, PostgresSieveQuotaDAO postgresSieveQuotaDAO) { this.transactionRunner = new TransactionRunner(entityManagerFactory); + this.postgresSieveQuotaDAO = postgresSieveQuotaDAO; } @Override @@ -85,10 +85,11 @@ public class JPASieveRepository implements SieveRepository { } } - private QuotaSizeLimit limitToUser(Username username) throws StorageException { - return findQuotaForUser(username.asString()) - .or(Throwing.supplier(() -> findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME)).sneakyThrow()) - .map(JPASieveQuota::toQuotaSize) + private QuotaSizeLimit limitToUser(Username username) { + return postgresSieveQuotaDAO.getQuota(username) + .filter(Optional::isPresent) + .switchIfEmpty(postgresSieveQuotaDAO.getGlobalQuota()) + .block() .orElse(QuotaSizeLimit.unlimited()); } @@ -99,7 +100,7 @@ public class JPASieveRepository implements SieveRepository { } @Override - public void putScript(Username username, ScriptName name, ScriptContent content) throws StorageException, QuotaExceededException { + public void putScript(Username username, ScriptName name, ScriptContent content) { transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> { try { haveSpace(username, name, content.length()); @@ -117,7 +118,7 @@ public class JPASieveRepository implements SieveRepository { } @Override - public List<ScriptSummary> listScripts(Username username) throws StorageException { + public List<ScriptSummary> listScripts(Username username) { return findAllSieveScriptsForUser(username).stream() .map(JPASieveScript::toSummary) .collect(ImmutableList.toImmutableList()); @@ -128,7 +129,7 @@ public class JPASieveRepository implements SieveRepository { return Mono.fromCallable(() -> listScripts(username)).flatMapMany(Flux::fromIterable); } - private List<JPASieveScript> findAllSieveScriptsForUser(Username username) throws StorageException { + private List<JPASieveScript> findAllSieveScriptsForUser(Username username) { return transactionRunner.runAndRetrieveResult(entityManager -> { List<JPASieveScript> sieveScripts = entityManager.createNamedQuery("findAllByUsername", JPASieveScript.class) .setParameter("username", username.asString()).getResultList(); @@ -137,26 +138,26 @@ public class JPASieveRepository implements SieveRepository { } @Override - public ZonedDateTime getActivationDateForActiveScript(Username username) throws StorageException, ScriptNotFoundException { + public ZonedDateTime getActivationDateForActiveScript(Username username) throws ScriptNotFoundException { Optional<JPASieveScript> script = findActiveSieveScript(username); JPASieveScript activeSieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable to find active script for user " + username.asString())); return activeSieveScript.getActivationDateTime().toZonedDateTime(); } @Override - public InputStream getActive(Username username) throws ScriptNotFoundException, StorageException { + public InputStream getActive(Username username) throws ScriptNotFoundException { Optional<JPASieveScript> script = findActiveSieveScript(username); JPASieveScript activeSieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable to find active script for user " + username.asString())); return IOUtils.toInputStream(activeSieveScript.getScriptContent(), StandardCharsets.UTF_8); } - private Optional<JPASieveScript> findActiveSieveScript(Username username) throws StorageException { + private Optional<JPASieveScript> findActiveSieveScript(Username username) { return transactionRunner.runAndRetrieveResult( Throwing.<EntityManager, Optional<JPASieveScript>>function(entityManager -> findActiveSieveScript(username, entityManager)).sneakyThrow(), throwStorageException("Unable to find active script for user " + username.asString())); } - private Optional<JPASieveScript> findActiveSieveScript(Username username, EntityManager entityManager) throws StorageException { + private Optional<JPASieveScript> findActiveSieveScript(Username username, EntityManager entityManager) { try { JPASieveScript activeSieveScript = entityManager.createNamedQuery("findActiveByUsername", JPASieveScript.class) .setParameter("username", username.asString()).getSingleResult(); @@ -168,7 +169,7 @@ public class JPASieveRepository implements SieveRepository { } @Override - public void setActive(Username username, ScriptName name) throws ScriptNotFoundException, StorageException { + public void setActive(Username username, ScriptName name) { transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> { try { if (SieveRepository.NO_SCRIPT_NAME.equals(name)) { @@ -196,13 +197,13 @@ public class JPASieveRepository implements SieveRepository { } @Override - public InputStream getScript(Username username, ScriptName name) throws ScriptNotFoundException, StorageException { + public InputStream getScript(Username username, ScriptName name) throws ScriptNotFoundException { Optional<JPASieveScript> script = findSieveScript(username, name); JPASieveScript sieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable to find script " + name.getValue() + " for user " + username.asString())); return IOUtils.toInputStream(sieveScript.getScriptContent(), StandardCharsets.UTF_8); } - private Optional<JPASieveScript> findSieveScript(Username username, ScriptName scriptName) throws StorageException { + private Optional<JPASieveScript> findSieveScript(Username username, ScriptName scriptName) { return transactionRunner.runAndRetrieveResult(entityManager -> findSieveScript(username, scriptName, entityManager), throwStorageException("Unable to find script " + scriptName.getValue() + " for user " + username.asString())); } @@ -220,7 +221,7 @@ public class JPASieveRepository implements SieveRepository { } @Override - public void deleteScript(Username username, ScriptName name) throws ScriptNotFoundException, IsActiveException, StorageException { + public void deleteScript(Username username, ScriptName name) { transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> { Optional<JPASieveScript> sieveScript = findSieveScript(username, name, entityManager); if (!sieveScript.isPresent()) { @@ -237,7 +238,7 @@ public class JPASieveRepository implements SieveRepository { } @Override - public void renameScript(Username username, ScriptName oldName, ScriptName newName) throws ScriptNotFoundException, DuplicateException, StorageException { + public void renameScript(Username username, ScriptName oldName, ScriptName newName) { transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> { Optional<JPASieveScript> sieveScript = findSieveScript(username, oldName, entityManager); if (!sieveScript.isPresent()) { @@ -263,54 +264,56 @@ public class JPASieveRepository implements SieveRepository { } @Override - public boolean hasDefaultQuota() throws StorageException { - Optional<JPASieveQuota> defaultQuota = findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME); - return defaultQuota.isPresent(); + public boolean hasDefaultQuota() { + return postgresSieveQuotaDAO.getGlobalQuota() + .block() + .isPresent(); } @Override - public QuotaSizeLimit getDefaultQuota() throws QuotaNotFoundException, StorageException { - JPASieveQuota jpaSieveQuota = findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME) - .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for default user")); - return QuotaSizeLimit.size(jpaSieveQuota.getSize()); + public QuotaSizeLimit getDefaultQuota() throws QuotaNotFoundException { + return postgresSieveQuotaDAO.getGlobalQuota() + .block() + .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for default user")); } @Override - public void setDefaultQuota(QuotaSizeLimit quota) throws StorageException { - setQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME, quota); + public void setDefaultQuota(QuotaSizeLimit quota) { + postgresSieveQuotaDAO.setGlobalQuota(quota) + .block(); } @Override - public void removeQuota() throws QuotaNotFoundException, StorageException { - removeQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME); + public void removeQuota() { + postgresSieveQuotaDAO.removeGlobalQuota() + .block(); } @Override - public boolean hasQuota(Username username) throws StorageException { - Optional<JPASieveQuota> quotaForUser = findQuotaForUser(username.asString()); - return quotaForUser.isPresent(); - } + public boolean hasQuota(Username username) { + Mono<Boolean> hasUserQuota = postgresSieveQuotaDAO.getQuota(username).map(Optional::isPresent); + Mono<Boolean> hasGlobalQuota = postgresSieveQuotaDAO.getGlobalQuota().map(Optional::isPresent); - @Override - public QuotaSizeLimit getQuota(Username username) throws QuotaNotFoundException, StorageException { - JPASieveQuota jpaSieveQuota = findQuotaForUser(username.asString()) - .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for user " + username.asString())); - return QuotaSizeLimit.size(jpaSieveQuota.getSize()); + return hasUserQuota.zipWith(hasGlobalQuota, (a, b) -> a || b) + .block(); } @Override - public void setQuota(Username username, QuotaSizeLimit quota) throws StorageException { - setQuotaForUser(username.asString(), quota); + public QuotaSizeLimit getQuota(Username username) throws QuotaNotFoundException { + return postgresSieveQuotaDAO.getQuota(username) + .block() + .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for user " + username.asString())); } @Override - public void removeQuota(Username username) throws QuotaNotFoundException, StorageException { - removeQuotaForUser(username.asString()); + public void setQuota(Username username, QuotaSizeLimit quota) { + postgresSieveQuotaDAO.setQuota(username, quota) + .block(); } - private Optional<JPASieveQuota> findQuotaForUser(String username) throws StorageException { - return transactionRunner.runAndRetrieveResult(entityManager -> findQuotaForUser(username, entityManager), - throwStorageException("Unable to find quota for user " + username)); + @Override + public void removeQuota(Username username) { + postgresSieveQuotaDAO.removeQuota(username).block(); } private <T> Function<PersistenceException, T> throwStorageException(String message) { @@ -325,37 +328,6 @@ public class JPASieveRepository implements SieveRepository { }).sneakyThrow(); } - private Optional<JPASieveQuota> findQuotaForUser(String username, EntityManager entityManager) { - try { - JPASieveQuota sieveQuota = entityManager.createNamedQuery("findByUsername", JPASieveQuota.class) - .setParameter("username", username).getSingleResult(); - return Optional.of(sieveQuota); - } catch (NoResultException e) { - return Optional.empty(); - } - } - - private void setQuotaForUser(String username, QuotaSizeLimit quota) throws StorageException { - transactionRunner.runAndHandleException(Throwing.consumer(entityManager -> { - Optional<JPASieveQuota> sieveQuota = findQuotaForUser(username, entityManager); - if (sieveQuota.isPresent()) { - JPASieveQuota jpaSieveQuota = sieveQuota.get(); - jpaSieveQuota.setSize(quota); - entityManager.merge(jpaSieveQuota); - } else { - JPASieveQuota jpaSieveQuota = new JPASieveQuota(username, quota.asLong()); - entityManager.persist(jpaSieveQuota); - } - }), throwStorageExceptionConsumer("Unable to set quota for user " + username)); - } - - private void removeQuotaForUser(String username) throws StorageException { - transactionRunner.runAndHandleException(Throwing.consumer(entityManager -> { - Optional<JPASieveQuota> quotaForUser = findQuotaForUser(username, entityManager); - quotaForUser.ifPresent(entityManager::remove); - }), throwStorageExceptionConsumer("Unable to remove quota for user " + username)); - } - @Override public Mono<Void> resetSpaceUsedReactive(Username username, long spaceUsed) { return Mono.error(new UnsupportedOperationException()); diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/JPASieveScript.java similarity index 99% rename from server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java rename to server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/JPASieveScript.java index 72b5ba53f5..8575b34a17 100644 --- a/server/data/data-postgres/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java +++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/JPASieveScript.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.sieve.jpa.model; +package org.apache.james.sieve.postgres.model; import java.time.OffsetDateTime; import java.util.Objects; diff --git a/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAOTest.java b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAOTest.java new file mode 100644 index 0000000000..aaeb02af06 --- /dev/null +++ b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveQuotaDAOTest.java @@ -0,0 +1,163 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.sieve.postgres; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.backends.postgres.PostgresExtension; +import org.apache.james.backends.postgres.PostgresModule; +import org.apache.james.backends.postgres.quota.PostgresQuotaCurrentValueDAO; +import org.apache.james.backends.postgres.quota.PostgresQuotaLimitDAO; +import org.apache.james.backends.postgres.quota.PostgresQuotaModule; +import org.apache.james.core.Username; +import org.apache.james.core.quota.QuotaSizeLimit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class PostgresSieveQuotaDAOTest { + @RegisterExtension + static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresModule.aggregateModules(PostgresQuotaModule.MODULE)); + + private static final Username USERNAME = Username.of("user"); + private static final QuotaSizeLimit QUOTA_SIZE = QuotaSizeLimit.size(15L); + + private PostgresSieveQuotaDAO testee; + + @BeforeEach + void setup() { + testee = new PostgresSieveQuotaDAO(new PostgresQuotaCurrentValueDAO(postgresExtension.getPostgresExecutor()), + new PostgresQuotaLimitDAO(postgresExtension.getPostgresExecutor())); + } + + @Test + void getQuotaShouldReturnEmptyByDefault() { + assertThat(testee.getGlobalQuota().block()) + .isEmpty(); + } + + @Test + void getQuotaUserShouldReturnEmptyByDefault() { + assertThat(testee.getQuota(USERNAME).block()) + .isEmpty(); + } + + @Test + void getQuotaShouldReturnStoredValue() { + testee.setGlobalQuota(QUOTA_SIZE).block(); + + assertThat(testee.getGlobalQuota().block()) + .contains(QUOTA_SIZE); + } + + @Test + void getQuotaUserShouldReturnStoredValue() { + testee.setQuota(USERNAME, QUOTA_SIZE).block(); + + assertThat(testee.getQuota(USERNAME).block()) + .contains(QUOTA_SIZE); + } + + @Test + void removeQuotaShouldDeleteQuota() { + testee.setGlobalQuota(QUOTA_SIZE).block(); + + testee.removeGlobalQuota().block(); + + assertThat(testee.getGlobalQuota().block()) + .isEmpty(); + } + + @Test + void removeQuotaUserShouldDeleteQuotaUser() { + testee.setQuota(USERNAME, QUOTA_SIZE).block(); + + testee.removeQuota(USERNAME).block(); + + assertThat(testee.getQuota(USERNAME).block()) + .isEmpty(); + } + + @Test + void removeQuotaShouldWorkWhenNoneStore() { + testee.removeGlobalQuota().block(); + + assertThat(testee.getGlobalQuota().block()) + .isEmpty(); + } + + @Test + void removeQuotaUserShouldWorkWhenNoneStore() { + testee.removeQuota(USERNAME).block(); + + assertThat(testee.getQuota(USERNAME).block()) + .isEmpty(); + } + + @Test + void spaceUsedByShouldReturnZeroByDefault() { + assertThat(testee.spaceUsedBy(USERNAME).block()).isZero(); + } + + @Test + void spaceUsedByShouldReturnStoredValue() { + long spaceUsed = 18L; + + testee.updateSpaceUsed(USERNAME, spaceUsed).block(); + + assertThat(testee.spaceUsedBy(USERNAME).block()).isEqualTo(spaceUsed); + } + + @Test + void updateSpaceUsedShouldBeAdditive() { + long spaceUsed = 18L; + + testee.updateSpaceUsed(USERNAME, spaceUsed).block(); + testee.updateSpaceUsed(USERNAME, spaceUsed).block(); + + assertThat(testee.spaceUsedBy(USERNAME).block()).isEqualTo(2 * spaceUsed); + } + + @Test + void updateSpaceUsedShouldWorkWithNegativeValues() { + long spaceUsed = 18L; + + testee.updateSpaceUsed(USERNAME, spaceUsed).block(); + testee.updateSpaceUsed(USERNAME, -1 * spaceUsed).block(); + + assertThat(testee.spaceUsedBy(USERNAME).block()).isZero(); + } + + @Test + void resetSpaceUsedShouldResetSpaceWhenNewSpaceIsGreaterThanCurrentSpace() { + testee.updateSpaceUsed(USERNAME, 10L).block(); + testee.resetSpaceUsed(USERNAME, 15L).block(); + + assertThat(testee.spaceUsedBy(USERNAME).block()).isEqualTo(15L); + } + + @Test + void resetSpaceUsedShouldResetSpaceWhenNewSpaceIsSmallerThanCurrentSpace() { + testee.updateSpaceUsed(USERNAME, 10L).block(); + testee.resetSpaceUsed(USERNAME, 9L).block(); + + assertThat(testee.spaceUsedBy(USERNAME).block()).isEqualTo(9L); + } +} diff --git a/server/data/data-postgres/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java similarity index 60% rename from server/data/data-postgres/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java rename to server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java index ab59dc651c..b31b1e173a 100644 --- a/server/data/data-postgres/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java +++ b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java @@ -17,30 +17,39 @@ * under the License. * ****************************************************************/ -package org.apache.james.sieve.jpa; +package org.apache.james.sieve.postgres; import org.apache.james.backends.jpa.JpaTestCluster; -import org.apache.james.sieve.jpa.model.JPASieveQuota; -import org.apache.james.sieve.jpa.model.JPASieveScript; +import org.apache.james.backends.postgres.PostgresExtension; +import org.apache.james.backends.postgres.PostgresModule; +import org.apache.james.backends.postgres.quota.PostgresQuotaCurrentValueDAO; +import org.apache.james.backends.postgres.quota.PostgresQuotaLimitDAO; +import org.apache.james.backends.postgres.quota.PostgresQuotaModule; +import org.apache.james.sieve.postgres.model.JPASieveScript; import org.apache.james.sieverepository.api.SieveRepository; import org.apache.james.sieverepository.lib.SieveRepositoryContract; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; -class JpaSieveRepositoryTest implements SieveRepositoryContract { +class PostgresSieveRepositoryTest implements SieveRepositoryContract { + @RegisterExtension + static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresModule.aggregateModules(PostgresQuotaModule.MODULE)); - final JpaTestCluster JPA_TEST_CLUSTER = JpaTestCluster.create(JPASieveScript.class, JPASieveQuota.class); + final JpaTestCluster JPA_TEST_CLUSTER = JpaTestCluster.create(JPASieveScript.class); SieveRepository sieveRepository; @BeforeEach void setUp() { - sieveRepository = new JPASieveRepository(JPA_TEST_CLUSTER.getEntityManagerFactory()); + sieveRepository = new PostgresSieveRepository(JPA_TEST_CLUSTER.getEntityManagerFactory(), + new PostgresSieveQuotaDAO(new PostgresQuotaCurrentValueDAO(postgresExtension.getPostgresExecutor()), + new PostgresQuotaLimitDAO(postgresExtension.getPostgresExecutor()))); } @AfterEach void tearDown() { - JPA_TEST_CLUSTER.clear("JAMES_SIEVE_SCRIPT", "JAMES_SIEVE_QUOTA"); + JPA_TEST_CLUSTER.clear("JAMES_SIEVE_SCRIPT"); } @Override diff --git a/server/data/data-postgres/src/test/resources/persistence.xml b/server/data/data-postgres/src/test/resources/persistence.xml index 6224adb74f..962146a543 100644 --- a/server/data/data-postgres/src/test/resources/persistence.xml +++ b/server/data/data-postgres/src/test/resources/persistence.xml @@ -27,12 +27,10 @@ <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/james)</jta-data-source> <class>org.apache.james.domainlist.jpa.model.JPADomain</class> - <class>org.apache.james.user.jpa.model.JPAUser</class> <class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class> <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class> <class>org.apache.james.mailrepository.jpa.model.JPAMail</class> - <class>org.apache.james.sieve.jpa.model.JPASieveQuota</class> - <class>org.apache.james.sieve.jpa.model.JPASieveScript</class> + <class>org.apache.james.sieve.postgres.model.JPASieveScript</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
