You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/12/18 07:47:35 UTC
(james-project) 01/03: JAMES-2586 Implement PostgresSieveScriptDAO + PostgresSieveRepository
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch postgresql
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4e56c426df82e2adad36a0bbc019b44e02ccf260
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Dec 11 17:45:11 2023 +0700
JAMES-2586 Implement PostgresSieveScriptDAO + PostgresSieveRepository
---
.../backends/postgres/utils/PostgresExecutor.java | 8 +
.../lib/SieveRepositoryContract.java | 11 +
.../james/sieve/postgres/PostgresSieveModule.java | 65 +++++
.../sieve/postgres/PostgresSieveRepository.java | 279 ++++++++-------------
.../sieve/postgres/PostgresSieveScriptDAO.java | 152 +++++++++++
.../sieve/postgres/model/PostgresSieveScript.java | 148 +++++++++++
.../postgres/PostgresSieveRepositoryTest.java | 18 +-
7 files changed, 493 insertions(+), 188 deletions(-)
diff --git a/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/utils/PostgresExecutor.java b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/utils/PostgresExecutor.java
index 686145dbeb..dceb4ac06f 100644
--- a/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/utils/PostgresExecutor.java
+++ b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/utils/PostgresExecutor.java
@@ -111,6 +111,14 @@ public class PostgresExecutor {
.map(Record1::value1);
}
+ public Mono<Long> executeReturnAffectedRowsCount(Function<DSLContext, Mono<Integer>> queryFunction) {
+ return dslContext()
+ .flatMap(queryFunction)
+ .cast(Long.class)
+ .retryWhen(Retry.backoff(MAX_RETRY_ATTEMPTS, MIN_BACKOFF)
+ .filter(preparedStatementConflictException()));
+ }
+
public Mono<Connection> connection() {
return connection;
}
diff --git a/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/SieveRepositoryContract.java b/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/SieveRepositoryContract.java
index 97f5841423..91531749a6 100644
--- a/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/SieveRepositoryContract.java
+++ b/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/SieveRepositoryContract.java
@@ -185,6 +185,17 @@ public interface SieveRepositoryContract {
.isInstanceOf(ScriptNotFoundException.class);
}
+ @Test
+ default void setActiveScriptOnNonExistingScriptShouldNotDeactivateTheCurrentActiveScript() throws Exception {
+ sieveRepository().putScript(USERNAME, SCRIPT_NAME, SCRIPT_CONTENT);
+ sieveRepository().setActive(USERNAME, SCRIPT_NAME);
+
+ assertThatThrownBy(() -> sieveRepository().setActive(USERNAME, OTHER_SCRIPT_NAME))
+ .isInstanceOf(ScriptNotFoundException.class);
+
+ assertThat(getScriptContent(sieveRepository().getActive(USERNAME))).isEqualTo(SCRIPT_CONTENT);
+ }
+
@Test
default void setActiveScriptShouldWork() throws Exception {
sieveRepository().putScript(USERNAME, SCRIPT_NAME, SCRIPT_CONTENT);
diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveModule.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveModule.java
new file mode 100644
index 0000000000..b6780f9e63
--- /dev/null
+++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveModule.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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 java.time.OffsetDateTime;
+
+import org.apache.james.backends.postgres.PostgresIndex;
+import org.apache.james.backends.postgres.PostgresModule;
+import org.apache.james.backends.postgres.PostgresTable;
+import org.jooq.Field;
+import org.jooq.Record;
+import org.jooq.Table;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+
+public interface PostgresSieveModule {
+ interface PostgresSieveScriptTable {
+ Table<Record> TABLE_NAME = DSL.table("sieve_scripts");
+
+ Field<String> USERNAME = DSL.field("username", SQLDataType.VARCHAR(255).notNull());
+ Field<String> SCRIPT_NAME = DSL.field("script_name", SQLDataType.VARCHAR.notNull());
+ Field<Long> SCRIPT_SIZE = DSL.field("script_size", SQLDataType.BIGINT.notNull());
+ Field<String> SCRIPT_CONTENT = DSL.field("script_content", SQLDataType.VARCHAR.notNull());
+ Field<Boolean> IS_ACTIVE = DSL.field("is_active", SQLDataType.BOOLEAN.notNull());
+ Field<OffsetDateTime> ACTIVATION_DATE_TIME = DSL.field("activation_date_time", SQLDataType.OFFSETDATETIME);
+
+ PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName())
+ .createTableStep(((dsl, tableName) -> dsl.createTableIfNotExists(tableName)
+ .column(USERNAME)
+ .column(SCRIPT_NAME)
+ .column(SCRIPT_SIZE)
+ .column(SCRIPT_CONTENT)
+ .column(IS_ACTIVE)
+ .column(ACTIVATION_DATE_TIME)
+ .primaryKey(USERNAME, SCRIPT_NAME)))
+ .disableRowLevelSecurity();
+
+ PostgresIndex MAXIMUM_ONE_ACTIVE_SCRIPT_PER_USER_UNIQUE_INDEX = PostgresIndex.name("maximum_one_active_script_per_user")
+ .createIndexStep(((dsl, indexName) -> dsl.createUniqueIndexIfNotExists(indexName)
+ .on(TABLE_NAME, USERNAME)
+ .where(IS_ACTIVE)));
+ }
+
+ PostgresModule MODULE = PostgresModule.builder()
+ .addTable(PostgresSieveScriptTable.TABLE)
+ .addIndex(PostgresSieveScriptTable.MAXIMUM_ONE_ACTIVE_SCRIPT_PER_USER_UNIQUE_INDEX)
+ .build();
+}
diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java
index 544ef43999..662915c523 100644
--- a/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java
+++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveRepository.java
@@ -19,27 +19,21 @@
package org.apache.james.sieve.postgres;
+import static org.apache.james.backends.postgres.utils.PostgresUtils.UNIQUE_CONSTRAINT_VIOLATION_PREDICATE;
+
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.Function;
import javax.inject.Inject;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
-import javax.persistence.EntityTransaction;
-import javax.persistence.NoResultException;
-import javax.persistence.PersistenceException;
import org.apache.commons.io.IOUtils;
-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.postgres.model.JPASieveScript;
+import org.apache.james.sieve.postgres.model.PostgresSieveScript;
import org.apache.james.sieverepository.api.ScriptContent;
import org.apache.james.sieverepository.api.ScriptName;
import org.apache.james.sieverepository.api.ScriptSummary;
@@ -49,217 +43,166 @@ import org.apache.james.sieverepository.api.exception.IsActiveException;
import org.apache.james.sieverepository.api.exception.QuotaExceededException;
import org.apache.james.sieverepository.api.exception.QuotaNotFoundException;
import org.apache.james.sieverepository.api.exception.ScriptNotFoundException;
-import org.apache.james.sieverepository.api.exception.StorageException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.github.fge.lambdas.Throwing;
-import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class PostgresSieveRepository implements SieveRepository {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(PostgresSieveRepository.class);
-
- private final TransactionRunner transactionRunner;
private final PostgresSieveQuotaDAO postgresSieveQuotaDAO;
+ private final PostgresSieveScriptDAO postgresSieveScriptDAO;
@Inject
- public PostgresSieveRepository(EntityManagerFactory entityManagerFactory, PostgresSieveQuotaDAO postgresSieveQuotaDAO) {
- this.transactionRunner = new TransactionRunner(entityManagerFactory);
+ public PostgresSieveRepository(PostgresSieveQuotaDAO postgresSieveQuotaDAO,
+ PostgresSieveScriptDAO postgresSieveScriptDAO) {
this.postgresSieveQuotaDAO = postgresSieveQuotaDAO;
+ this.postgresSieveScriptDAO = postgresSieveScriptDAO;
}
@Override
- public void haveSpace(Username username, ScriptName name, long size) throws QuotaExceededException, StorageException {
- long usedSpace = findAllSieveScriptsForUser(username).stream()
- .filter(sieveScript -> !sieveScript.getScriptName().equals(name.getValue()))
- .mapToLong(JPASieveScript::getScriptSize)
- .sum();
-
- QuotaSizeLimit quota = limitToUser(username);
- if (overQuotaAfterModification(usedSpace, size, quota)) {
- throw new QuotaExceededException();
+ public void haveSpace(Username username, ScriptName name, long size) throws QuotaExceededException {
+ long sizeDifference = spaceThatWillBeUsedByNewScript(username, name, size).block();
+ throwOnOverQuota(username, sizeDifference);
+ }
+
+ @Override
+ public void putScript(Username username, ScriptName name, ScriptContent content) throws QuotaExceededException {
+ long sizeDifference = spaceThatWillBeUsedByNewScript(username, name, content.length()).block();
+ throwOnOverQuota(username, sizeDifference);
+ postgresSieveScriptDAO.upsertScript(PostgresSieveScript.builder()
+ .username(username.asString())
+ .scriptName(name.getValue())
+ .scriptContent(content.getValue())
+ .scriptSize(content.length())
+ .isActive(false)
+ .build())
+ .flatMap(upsertedScripts -> {
+ if (upsertedScripts > 0) {
+ return updateSpaceUsed(username, sizeDifference);
+ }
+ return Mono.empty();
+ })
+ .block();
+ }
+
+ private Mono<Void> updateSpaceUsed(Username username, long spaceToUse) {
+ if (spaceToUse == 0) {
+ return Mono.empty();
}
+ return postgresSieveQuotaDAO.updateSpaceUsed(username, spaceToUse);
}
- private QuotaSizeLimit limitToUser(Username username) {
- return postgresSieveQuotaDAO.getQuota(username)
- .filter(Optional::isPresent)
- .switchIfEmpty(postgresSieveQuotaDAO.getGlobalQuota())
- .block()
- .orElse(QuotaSizeLimit.unlimited());
+ private Mono<Long> spaceThatWillBeUsedByNewScript(Username username, ScriptName name, long scriptSize) {
+ return postgresSieveScriptDAO.getScriptSize(username, name)
+ .defaultIfEmpty(0L)
+ .map(sizeOfStoredScript -> scriptSize - sizeOfStoredScript);
}
- private boolean overQuotaAfterModification(long usedSpace, long size, QuotaSizeLimit quota) {
- return QuotaSizeUsage.size(usedSpace)
- .add(size)
- .exceedLimit(quota);
+ private void throwOnOverQuota(Username username, Long sizeDifference) throws QuotaExceededException {
+ long spaceUsed = postgresSieveQuotaDAO.spaceUsedBy(username).block();
+ QuotaSizeLimit limit = limitToUser(username).block();
+
+ if (QuotaSizeUsage.size(spaceUsed)
+ .add(sizeDifference)
+ .exceedLimit(limit)) {
+ throw new QuotaExceededException();
+ }
}
- @Override
- public void putScript(Username username, ScriptName name, ScriptContent content) {
- transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> {
- try {
- haveSpace(username, name, content.length());
- JPASieveScript jpaSieveScript = JPASieveScript.builder()
- .username(username.asString())
- .scriptName(name.getValue())
- .scriptContent(content)
- .build();
- entityManager.persist(jpaSieveScript);
- } catch (QuotaExceededException | StorageException e) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw e;
- }
- }).sneakyThrow(), throwStorageExceptionConsumer("Unable to put script for user " + username.asString()));
+ private Mono<QuotaSizeLimit> limitToUser(Username username) {
+ return postgresSieveQuotaDAO.getQuota(username)
+ .filter(Optional::isPresent)
+ .switchIfEmpty(postgresSieveQuotaDAO.getGlobalQuota())
+ .map(optional -> optional.orElse(QuotaSizeLimit.unlimited()));
}
@Override
public List<ScriptSummary> listScripts(Username username) {
- return findAllSieveScriptsForUser(username).stream()
- .map(JPASieveScript::toSummary)
- .collect(ImmutableList.toImmutableList());
+ return listScriptsReactive(username)
+ .collectList()
+ .block();
}
@Override
public Flux<ScriptSummary> listScriptsReactive(Username username) {
- return Mono.fromCallable(() -> listScripts(username)).flatMapMany(Flux::fromIterable);
- }
-
- private List<JPASieveScript> findAllSieveScriptsForUser(Username username) {
- return transactionRunner.runAndRetrieveResult(entityManager -> {
- List<JPASieveScript> sieveScripts = entityManager.createNamedQuery("findAllByUsername", JPASieveScript.class)
- .setParameter("username", username.asString()).getResultList();
- return Optional.ofNullable(sieveScripts).orElse(ImmutableList.of());
- }, throwStorageException("Unable to list scripts for user " + username.asString()));
+ return postgresSieveScriptDAO.getScripts(username)
+ .map(PostgresSieveScript::toScriptSummary);
}
@Override
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();
+ return postgresSieveScriptDAO.getActiveScript(username)
+ .blockOptional()
+ .orElseThrow(ScriptNotFoundException::new)
+ .getActivationDateTime()
+ .toZonedDateTime();
}
@Override
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) {
- return transactionRunner.runAndRetrieveResult(
- Throwing.<EntityManager, Optional<JPASieveScript>>function(entityManager -> findActiveSieveScript(username, entityManager)).sneakyThrow(),
- throwStorageException("Unable to find active script for user " + username.asString()));
+ return IOUtils.toInputStream(postgresSieveScriptDAO.getActiveScript(username)
+ .blockOptional()
+ .orElseThrow(ScriptNotFoundException::new)
+ .getScriptContent(), StandardCharsets.UTF_8);
}
- private Optional<JPASieveScript> findActiveSieveScript(Username username, EntityManager entityManager) {
- try {
- JPASieveScript activeSieveScript = entityManager.createNamedQuery("findActiveByUsername", JPASieveScript.class)
- .setParameter("username", username.asString()).getSingleResult();
- return Optional.ofNullable(activeSieveScript);
- } catch (NoResultException e) {
- LOGGER.debug("Sieve script not found for user {}", username.asString());
- return Optional.empty();
+ @Override
+ public void setActive(Username username, ScriptName name) throws ScriptNotFoundException {
+ if (SieveRepository.NO_SCRIPT_NAME.equals(name)) {
+ switchOffCurrentActiveScript(username);
+ } else {
+ throwOnScriptNonExistence(username, name);
+ switchOffCurrentActiveScript(username);
+ activateScript(username, name);
}
}
- @Override
- public void setActive(Username username, ScriptName name) {
- transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> {
- try {
- if (SieveRepository.NO_SCRIPT_NAME.equals(name)) {
- switchOffActiveScript(username, entityManager);
- } else {
- setActiveScript(username, name, entityManager);
- }
- } catch (StorageException | ScriptNotFoundException e) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw e;
- }
- }).sneakyThrow(), throwStorageExceptionConsumer("Unable to set active script " + name.getValue() + " for user " + username.asString()));
+ private void throwOnScriptNonExistence(Username username, ScriptName name) throws ScriptNotFoundException {
+ if (!postgresSieveScriptDAO.scriptExists(username, name).block()) {
+ throw new ScriptNotFoundException();
+ }
}
- private void switchOffActiveScript(Username username, EntityManager entityManager) throws StorageException {
- Optional<JPASieveScript> activeSieveScript = findActiveSieveScript(username, entityManager);
- activeSieveScript.ifPresent(JPASieveScript::deactivate);
+ private void switchOffCurrentActiveScript(Username username) {
+ postgresSieveScriptDAO.deactivateCurrentActiveScript(username).block();
}
- private void setActiveScript(Username username, ScriptName name, EntityManager entityManager) throws StorageException, ScriptNotFoundException {
- JPASieveScript sieveScript = findSieveScript(username, name, entityManager)
- .orElseThrow(() -> new ScriptNotFoundException("Unable to find script " + name.getValue() + " for user " + username.asString()));
- findActiveSieveScript(username, entityManager).ifPresent(JPASieveScript::deactivate);
- sieveScript.activate();
+ private void activateScript(Username username, ScriptName scriptName) {
+ postgresSieveScriptDAO.activateScript(username, scriptName).block();
}
@Override
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);
+ return IOUtils.toInputStream(postgresSieveScriptDAO.getScript(username, name)
+ .blockOptional()
+ .orElseThrow(ScriptNotFoundException::new)
+ .getScriptContent(), StandardCharsets.UTF_8);
}
- 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()));
- }
+ @Override
+ public void deleteScript(Username username, ScriptName name) throws ScriptNotFoundException, IsActiveException {
+ boolean isActive = postgresSieveScriptDAO.getIsActive(username, name)
+ .blockOptional()
+ .orElseThrow(ScriptNotFoundException::new);
- private Optional<JPASieveScript> findSieveScript(Username username, ScriptName scriptName, EntityManager entityManager) {
- try {
- JPASieveScript sieveScript = entityManager.createNamedQuery("findSieveScript", JPASieveScript.class)
- .setParameter("username", username.asString())
- .setParameter("scriptName", scriptName.getValue()).getSingleResult();
- return Optional.ofNullable(sieveScript);
- } catch (NoResultException e) {
- LOGGER.debug("Sieve script not found for user {}", username.asString());
- return Optional.empty();
+ if (isActive) {
+ throw new IsActiveException();
}
- }
- @Override
- public void deleteScript(Username username, ScriptName name) {
- transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager -> {
- Optional<JPASieveScript> sieveScript = findSieveScript(username, name, entityManager);
- if (!sieveScript.isPresent()) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw new ScriptNotFoundException("Unable to find script " + name.getValue() + " for user " + username.asString());
- }
- JPASieveScript sieveScriptToRemove = sieveScript.get();
- if (sieveScriptToRemove.isActive()) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw new IsActiveException("Unable to delete active script " + name.getValue() + " for user " + username.asString());
- }
- entityManager.remove(sieveScriptToRemove);
- }).sneakyThrow(), throwStorageExceptionConsumer("Unable to delete script " + name.getValue() + " for user " + username.asString()));
+ postgresSieveScriptDAO.deleteScript(username, name).block();
}
@Override
- 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()) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw new ScriptNotFoundException("Unable to find script " + oldName.getValue() + " for user " + username.asString());
+ public void renameScript(Username username, ScriptName oldName, ScriptName newName) throws DuplicateException, ScriptNotFoundException {
+ try {
+ long renamedScripts = postgresSieveScriptDAO.renameScript(username, oldName, newName).block();
+ if (renamedScripts == 0) {
+ throw new ScriptNotFoundException();
}
-
- Optional<JPASieveScript> duplicatedSieveScript = findSieveScript(username, newName, entityManager);
- if (duplicatedSieveScript.isPresent()) {
- rollbackTransactionIfActive(entityManager.getTransaction());
- throw new DuplicateException("Unable to rename script. Duplicate found " + newName.getValue() + " for user " + username.asString());
+ } catch (Exception e) {
+ if (UNIQUE_CONSTRAINT_VIOLATION_PREDICATE.test(e)) {
+ throw new DuplicateException();
}
-
- JPASieveScript sieveScriptToRename = sieveScript.get();
- sieveScriptToRename.renameTo(newName);
- }).sneakyThrow(), throwStorageExceptionConsumer("Unable to rename script " + oldName.getValue() + " for user " + username.asString()));
- }
-
- private void rollbackTransactionIfActive(EntityTransaction transaction) {
- if (transaction.isActive()) {
- transaction.rollback();
+ throw e;
}
}
@@ -316,18 +259,6 @@ public class PostgresSieveRepository implements SieveRepository {
postgresSieveQuotaDAO.removeQuota(username).block();
}
- private <T> Function<PersistenceException, T> throwStorageException(String message) {
- return Throwing.<PersistenceException, T>function(e -> {
- throw new StorageException(message, e);
- }).sneakyThrow();
- }
-
- private Consumer<PersistenceException> throwStorageExceptionConsumer(String message) {
- return Throwing.<PersistenceException>consumer(e -> {
- throw new StorageException(message, e);
- }).sneakyThrow();
- }
-
@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/postgres/PostgresSieveScriptDAO.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveScriptDAO.java
new file mode 100644
index 0000000000..b87778db9f
--- /dev/null
+++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/PostgresSieveScriptDAO.java
@@ -0,0 +1,152 @@
+/****************************************************************
+ * 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.backends.postgres.utils.PostgresExecutor.DEFAULT_INJECT;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.ACTIVATION_DATE_TIME;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.IS_ACTIVE;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.SCRIPT_CONTENT;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.SCRIPT_NAME;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.SCRIPT_SIZE;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.TABLE_NAME;
+import static org.apache.james.sieve.postgres.PostgresSieveModule.PostgresSieveScriptTable.USERNAME;
+
+import java.time.OffsetDateTime;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.james.backends.postgres.utils.PostgresExecutor;
+import org.apache.james.core.Username;
+import org.apache.james.sieve.postgres.model.PostgresSieveScript;
+import org.apache.james.sieverepository.api.ScriptName;
+import org.jooq.Record;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class PostgresSieveScriptDAO {
+ private final PostgresExecutor postgresExecutor;
+
+ @Inject
+ public PostgresSieveScriptDAO(@Named(DEFAULT_INJECT) PostgresExecutor postgresExecutor) {
+ this.postgresExecutor = postgresExecutor;
+ }
+
+ public Mono<Long> upsertScript(PostgresSieveScript sieveScript) {
+ return postgresExecutor.executeReturnAffectedRowsCount(dslContext -> Mono.from(dslContext.insertInto(TABLE_NAME)
+ .set(USERNAME, sieveScript.getUsername())
+ .set(SCRIPT_NAME, sieveScript.getScriptName())
+ .set(SCRIPT_SIZE, sieveScript.getScriptSize())
+ .set(SCRIPT_CONTENT, sieveScript.getScriptContent())
+ .set(IS_ACTIVE, sieveScript.isActive())
+ .set(ACTIVATION_DATE_TIME, sieveScript.getActivationDateTime())
+ .onConflict(USERNAME, SCRIPT_NAME)
+ .doUpdate()
+ .set(SCRIPT_SIZE, sieveScript.getScriptSize())
+ .set(SCRIPT_CONTENT, sieveScript.getScriptContent())
+ .set(IS_ACTIVE, sieveScript.isActive())
+ .set(ACTIVATION_DATE_TIME, sieveScript.getActivationDateTime())));
+ }
+
+ public Mono<PostgresSieveScript> getScript(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.selectFrom(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))))
+ .map(recordToPostgresSieveScript());
+ }
+
+ public Mono<Long> getScriptSize(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.select(SCRIPT_SIZE)
+ .from(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))))
+ .map(record -> record.get(SCRIPT_SIZE));
+ }
+
+ public Mono<Boolean> getIsActive(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.select(IS_ACTIVE)
+ .from(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))))
+ .map(record -> record.get(IS_ACTIVE));
+ }
+
+ public Mono<Boolean> scriptExists(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeCount(dslContext -> Mono.from(dslContext.selectCount()
+ .from(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))))
+ .map(count -> count > 0);
+ }
+
+ public Flux<PostgresSieveScript> getScripts(Username username) {
+ return postgresExecutor.executeRows(dslContext -> Flux.from(dslContext.selectFrom(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()))))
+ .map(recordToPostgresSieveScript());
+ }
+
+ public Mono<PostgresSieveScript> getActiveScript(Username username) {
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.selectFrom(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ IS_ACTIVE.eq(true))))
+ .map(recordToPostgresSieveScript());
+ }
+
+ public Mono<Void> activateScript(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.update(TABLE_NAME)
+ .set(IS_ACTIVE, true)
+ .set(ACTIVATION_DATE_TIME, OffsetDateTime.now())
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))));
+ }
+
+ public Mono<Void> deactivateCurrentActiveScript(Username username) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.update(TABLE_NAME)
+ .set(IS_ACTIVE, false)
+ .where(USERNAME.eq(username.asString()),
+ IS_ACTIVE.eq(true))));
+ }
+
+ public Mono<Long> renameScript(Username username, ScriptName oldName, ScriptName newName) {
+ return postgresExecutor.executeReturnAffectedRowsCount(dslContext -> Mono.from(dslContext.update(TABLE_NAME)
+ .set(SCRIPT_NAME, newName.getValue())
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(oldName.getValue()))));
+ }
+
+ public Mono<Void> deleteScript(Username username, ScriptName scriptName) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.deleteFrom(TABLE_NAME)
+ .where(USERNAME.eq(username.asString()),
+ SCRIPT_NAME.eq(scriptName.getValue()))));
+ }
+
+ private Function<Record, PostgresSieveScript> recordToPostgresSieveScript() {
+ return record -> PostgresSieveScript.builder()
+ .username(record.get(USERNAME))
+ .scriptName(record.get(SCRIPT_NAME))
+ .scriptContent(record.get(SCRIPT_CONTENT))
+ .scriptSize(record.get(SCRIPT_SIZE))
+ .isActive(record.get(IS_ACTIVE))
+ .activationDateTime(record.get(ACTIVATION_DATE_TIME))
+ .build();
+ }
+}
diff --git a/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/PostgresSieveScript.java b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/PostgresSieveScript.java
new file mode 100644
index 0000000000..d5831d5464
--- /dev/null
+++ b/server/data/data-postgres/src/main/java/org/apache/james/sieve/postgres/model/PostgresSieveScript.java
@@ -0,0 +1,148 @@
+/****************************************************************
+ * 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.model;
+
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.james.sieverepository.api.ScriptName;
+import org.apache.james.sieverepository.api.ScriptSummary;
+
+import com.google.common.base.Preconditions;
+
+public class PostgresSieveScript {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String username;
+ private String scriptName;
+ private String scriptContent;
+ private long scriptSize;
+ private boolean isActive;
+ private OffsetDateTime activationDateTime;
+
+ public Builder username(String username) {
+ Preconditions.checkNotNull(username);
+ this.username = username;
+ return this;
+ }
+
+ public Builder scriptName(String scriptName) {
+ Preconditions.checkNotNull(scriptName);
+ this.scriptName = scriptName;
+ return this;
+ }
+
+ public Builder scriptContent(String scriptContent) {
+ this.scriptContent = scriptContent;
+ return this;
+ }
+
+ public Builder scriptSize(long scriptSize) {
+ this.scriptSize = scriptSize;
+ return this;
+ }
+
+ public Builder isActive(boolean isActive) {
+ this.isActive = isActive;
+ return this;
+ }
+
+ public Builder activationDateTime(OffsetDateTime offsetDateTime) {
+ this.activationDateTime = offsetDateTime;
+ return this;
+ }
+
+ public PostgresSieveScript build() {
+ Preconditions.checkState(StringUtils.isNotBlank(username), "'username' is mandatory");
+ Preconditions.checkState(StringUtils.isNotBlank(scriptName), "'scriptName' is mandatory");
+
+ return new PostgresSieveScript(username, scriptName, scriptContent, scriptSize, isActive, activationDateTime);
+ }
+ }
+
+ private final String username;
+ private final String scriptName;
+ private final String scriptContent;
+ private final long scriptSize;
+ private final boolean isActive;
+ private final OffsetDateTime activationDateTime;
+
+ private PostgresSieveScript(String username, String scriptName, String scriptContent, long scriptSize, boolean isActive, OffsetDateTime activationDateTime) {
+ this.username = username;
+ this.scriptName = scriptName;
+ this.scriptContent = scriptContent;
+ this.scriptSize = scriptSize;
+ this.isActive = isActive;
+ this.activationDateTime = activationDateTime;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getScriptName() {
+ return scriptName;
+ }
+
+ public String getScriptContent() {
+ return scriptContent;
+ }
+
+ public long getScriptSize() {
+ return scriptSize;
+ }
+
+ public boolean isActive() {
+ return isActive;
+ }
+
+ public OffsetDateTime getActivationDateTime() {
+ return activationDateTime;
+ }
+
+ public ScriptSummary toScriptSummary() {
+ return new ScriptSummary(new ScriptName(scriptName), isActive, scriptSize);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof PostgresSieveScript) {
+ PostgresSieveScript that = (PostgresSieveScript) o;
+
+ return Objects.equals(this.scriptSize, that.scriptSize)
+ && Objects.equals(this.isActive, that.isActive)
+ && Objects.equals(this.username, that.username)
+ && Objects.equals(this.scriptName, that.scriptName)
+ && Objects.equals(this.scriptContent, that.scriptContent)
+ && Objects.equals(this.activationDateTime, that.activationDateTime);
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(username, scriptName);
+ }
+}
diff --git a/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java
index b31b1e173a..d67c71069e 100644
--- a/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java
+++ b/server/data/data-postgres/src/test/java/org/apache/james/sieve/postgres/PostgresSieveRepositoryTest.java
@@ -19,37 +19,27 @@
package org.apache.james.sieve.postgres;
-import org.apache.james.backends.jpa.JpaTestCluster;
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 PostgresSieveRepositoryTest implements SieveRepositoryContract {
@RegisterExtension
- static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresModule.aggregateModules(PostgresQuotaModule.MODULE));
-
- final JpaTestCluster JPA_TEST_CLUSTER = JpaTestCluster.create(JPASieveScript.class);
+ static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresModule.aggregateModules(PostgresQuotaModule.MODULE,
+ PostgresSieveModule.MODULE));
SieveRepository sieveRepository;
@BeforeEach
void setUp() {
- 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");
+ sieveRepository = new PostgresSieveRepository(new PostgresSieveQuotaDAO(new PostgresQuotaCurrentValueDAO(postgresExtension.getPostgresExecutor()), new PostgresQuotaLimitDAO(postgresExtension.getPostgresExecutor())),
+ new PostgresSieveScriptDAO(postgresExtension.getPostgresExecutor()));
}
@Override
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org