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]


Reply via email to