james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From btell...@apache.org
Subject [01/17] james-project git commit: JAMES-2155 Implements a JPA Sieve Repository
Date Mon, 15 Oct 2018 06:28:24 GMT
Repository: james-project
Updated Branches:
  refs/heads/master 02f8d6f73 -> 45d311c55


JAMES-2155 Implements a JPA Sieve Repository


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

Branch: refs/heads/master
Commit: d5d5c9cb86dce4edda511b2a43a9e753ca777256
Parents: 02f8d6f
Author: Sebastian Górecki <gorecki.sebastian@gmail.com>
Authored: Wed Sep 5 22:53:19 2018 +0200
Committer: Benoit Tellier <btellier@linagora.com>
Committed: Mon Oct 15 13:15:22 2018 +0700

----------------------------------------------------------------------
 .../james/backends/jpa/TransactionRunner.java   |  18 +
 server/container/guice/guice-common/pom.xml     |   4 +
 .../modules/data/SieveJPARepositoryModules.java |  37 ++
 .../org/apache/james/JPAJamesServerMain.java    |   8 +-
 server/data/data-jpa/pom.xml                    |   8 +-
 .../james/sieve/jpa/JPASieveRepository.java     | 344 +++++++++++++++++++
 .../james/sieve/jpa/model/JPASieveQuota.java    |  97 ++++++
 .../james/sieve/jpa/model/JPASieveScript.java   | 200 +++++++++++
 .../src/main/resources/META-INF/persistence.xml |   2 +
 .../james/sieve/jpa/JpaSieveRepositoryTest.java |  50 +++
 .../lib/AbstractSieveRepositoryTest.java        |   7 +
 11 files changed, 769 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/backends-common/jpa/src/main/java/org/apache/james/backends/jpa/TransactionRunner.java
----------------------------------------------------------------------
diff --git a/backends-common/jpa/src/main/java/org/apache/james/backends/jpa/TransactionRunner.java
b/backends-common/jpa/src/main/java/org/apache/james/backends/jpa/TransactionRunner.java
index dae6f2e..dd1e151 100644
--- a/backends-common/jpa/src/main/java/org/apache/james/backends/jpa/TransactionRunner.java
+++ b/backends-common/jpa/src/main/java/org/apache/james/backends/jpa/TransactionRunner.java
@@ -76,4 +76,22 @@ public class TransactionRunner {
         }
     }
 
+    public <T> void runAndHandleException(Consumer<EntityManager> runnable,
+                                          Function<PersistenceException, T> errorHandler)
{
+        EntityManager entityManager = entityManagerFactory.createEntityManager();
+        EntityTransaction transaction = entityManager.getTransaction();
+        try {
+            transaction.begin();
+            runnable.accept(entityManager);
+            transaction.commit();
+        } catch (PersistenceException e) {
+            LOGGER.warn("Could not execute transaction", e);
+            if (transaction.isActive()) {
+                transaction.rollback();
+            }
+            errorHandler.apply(e);
+        } finally {
+            entityManager.close();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/container/guice/guice-common/pom.xml
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/pom.xml b/server/container/guice/guice-common/pom.xml
index b7bdd99..0559b85 100644
--- a/server/container/guice/guice-common/pom.xml
+++ b/server/container/guice/guice-common/pom.xml
@@ -51,6 +51,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-memory</artifactId>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/container/guice/guice-common/src/main/java/org/apache/james/modules/data/SieveJPARepositoryModules.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/modules/data/SieveJPARepositoryModules.java
b/server/container/guice/guice-common/src/main/java/org/apache/james/modules/data/SieveJPARepositoryModules.java
new file mode 100644
index 0000000..2d984e3
--- /dev/null
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/modules/data/SieveJPARepositoryModules.java
@@ -0,0 +1,37 @@
+/****************************************************************
+ * 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.modules.data;
+
+import org.apache.james.sieve.jpa.JPASieveRepository;
+import org.apache.james.sieverepository.api.SieveQuotaRepository;
+import org.apache.james.sieverepository.api.SieveRepository;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+
+public class SieveJPARepositoryModules extends AbstractModule {
+    @Override
+    protected void configure() {
+        bind(JPASieveRepository.class).in(Scopes.SINGLETON);
+
+        bind(SieveRepository.class).to(JPASieveRepository.class);
+        bind(SieveQuotaRepository.class).to(JPASieveRepository.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
----------------------------------------------------------------------
diff --git a/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
b/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
index 61eba68..6a5ba46 100644
--- a/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
+++ b/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
@@ -22,7 +22,7 @@ package org.apache.james;
 import org.apache.james.modules.MailboxModule;
 import org.apache.james.modules.activemq.ActiveMQQueueModule;
 import org.apache.james.modules.data.JPADataModule;
-import org.apache.james.modules.data.SieveFileRepositoryModule;
+import org.apache.james.modules.data.SieveJPARepositoryModules;
 import org.apache.james.modules.mailbox.DefaultEventModule;
 import org.apache.james.modules.mailbox.JPAMailboxModule;
 import org.apache.james.modules.mailbox.LuceneSearchMailboxModule;
@@ -69,7 +69,7 @@ public class JPAJamesServerMain {
         new ProtocolHandlerModule(),
         new SMTPServerModule(),
         WEBADMIN);
-    
+
     public static final Module JPA_SERVER_MODULE = Modules.combine(
         new ActiveMQQueueModule(),
         new DefaultProcessorsConfigurationProviderModule(),
@@ -79,7 +79,7 @@ public class JPAJamesServerMain {
         new MailboxModule(),
         new NoJwtModule(),
         new RawPostDequeueDecoratorModule(),
-        new SieveFileRepositoryModule(),
+        new SieveJPARepositoryModules(),
         new DefaultEventModule(),
         new SpamAssassinListenerModule());
 
@@ -90,7 +90,7 @@ public class JPAJamesServerMain {
 
         GuiceJamesServer server = GuiceJamesServer.forConfiguration(configuration)
                     .combineWith(JPA_SERVER_MODULE, PROTOCOLS,
-                            new JMXServerModule(), 
+                            new JMXServerModule(),
                             new LuceneSearchMailboxModule());
         server.start();
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/pom.xml
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/pom.xml b/server/data/data-jpa/pom.xml
index cb0cd25..5d1c3ad 100644
--- a/server/data/data-jpa/pom.xml
+++ b/server/data/data-jpa/pom.xml
@@ -152,7 +152,9 @@
                 <artifactId>openjpa-maven-plugin</artifactId>
                 <version>${apache.openjpa.version}</version>
                 <configuration>
-                    <includes>org/apache/james/user/jpa/model/JPAUser.class,
+                    <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,
                         org/apache/james/rrt/jpa/model/JPARecipientRewrite.class,
                         org/apache/james/domainlist/jpa/model/JPADomain.class,
                         org/apache/james/mailrepository/jpa/JPAUrl.class</includes>
@@ -165,7 +167,9 @@
                         </property>
                         <property>
                             <name>metaDataFactory</name>
-                            <value>jpa(Types=org.apache.james.user.jpa.model.JPAUser;
+                            <value>jpa(Types=org.apache.james.sieve.jpa.model.JPASieveQuota;
+                                org.apache.james.sieve.jpa.model.JPASieveScript;
+                                org.apache.james.user.jpa.model.JPAUser;
                                 org.apache.james.rrt.jpa.model.JPARecipientRewrite;
                                 org.apache.james.domainlist.jpa.model.JPADomain;
                                 org.apache.james.mailrepository.jpa.JPAUrl)</value>

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java
b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java
new file mode 100644
index 0000000..31c634a
--- /dev/null
+++ b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/JPASieveRepository.java
@@ -0,0 +1,344 @@
+/****************************************************************
+ * 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;
+
+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.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.User;
+import org.apache.james.core.quota.QuotaSize;
+import org.apache.james.sieve.jpa.model.JPASieveQuota;
+import org.apache.james.sieve.jpa.model.JPASieveScript;
+import org.apache.james.sieverepository.api.ScriptContent;
+import org.apache.james.sieverepository.api.ScriptName;
+import org.apache.james.sieverepository.api.ScriptSummary;
+import org.apache.james.sieverepository.api.SieveRepository;
+import org.apache.james.sieverepository.api.exception.DuplicateException;
+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.apache.james.util.OptionalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
+
+public class JPASieveRepository implements SieveRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JPASieveRepository.class);
+    private static final String DEFAULT_SIEVE_QUOTA_USERNAME = "default.quota";
+
+    private final TransactionRunner transactionRunner;
+
+    @Inject
+    public JPASieveRepository(EntityManagerFactory entityManagerFactory) {
+        this.transactionRunner = new TransactionRunner(entityManagerFactory);
+    }
+
+    @Override
+    public void haveSpace(User user, ScriptName name, long size) throws QuotaExceededException,
StorageException {
+        long usedSpace = findAllSieveScriptsForUser(user).stream()
+                .filter(sieveScript -> !sieveScript.getScriptName().equals(name.getValue()))
+                .mapToLong(JPASieveScript::getScriptSize)
+                .sum();
+
+        QuotaSize quota = limitToUser(user);
+        if (overQuotaAfterModification(usedSpace, size, quota)) {
+            throw new QuotaExceededException();
+        }
+    }
+
+    private QuotaSize limitToUser(User user) throws StorageException {
+        return OptionalUtils.orSuppliers(
+                Throwing.supplier(() -> findQuotaForUser(user.asString())).sneakyThrow(),
+                Throwing.supplier(() -> findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME)).sneakyThrow())
+                .map(JPASieveQuota::toQuotaSize)
+                .orElse(QuotaSize.unlimited());
+    }
+
+    private boolean overQuotaAfterModification(long usedSpace, long size, QuotaSize quota)
{
+        return QuotaSize.size(usedSpace)
+                .add(size)
+                .isGreaterThan(quota);
+    }
+
+    @Override
+    public void putScript(User user, ScriptName name, ScriptContent content) throws StorageException,
QuotaExceededException {
+        transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager
-> {
+            try {
+                haveSpace(user, name, content.length());
+                JPASieveScript jpaSieveScript = JPASieveScript.builder()
+                        .username(user.asString())
+                        .scriptName(name.getValue())
+                        .scriptContent(content)
+                        .build();
+                entityManager.persist(jpaSieveScript);
+            } catch (QuotaExceededException | StorageException e) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw e;
+            }
+        }).sneakyThrow(), throwStorageException("Unable to put script for user " + user.asString()));
+    }
+
+    @Override
+    public List<ScriptSummary> listScripts(User user) throws StorageException {
+        return findAllSieveScriptsForUser(user).stream()
+                .map(JPASieveScript::toSummary)
+                .collect(ImmutableList.toImmutableList());
+    }
+
+    private List<JPASieveScript> findAllSieveScriptsForUser(User user) throws StorageException
{
+        return transactionRunner.runAndRetrieveResult(entityManager -> {
+            List<JPASieveScript> sieveScripts = entityManager.createNamedQuery("findAllByUsername",
JPASieveScript.class)
+                    .setParameter("username", user.asString()).getResultList();
+            return Optional.ofNullable(sieveScripts).orElse(ImmutableList.of());
+        }, throwStorageException("Unable to list scripts for user " + user.asString()));
+    }
+
+    @Override
+    public ZonedDateTime getActivationDateForActiveScript(User user) throws StorageException,
ScriptNotFoundException {
+        Optional<JPASieveScript> script = findActiveSieveScript(user);
+        JPASieveScript activeSieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable
to find active script for user " + user.asString()));
+        return activeSieveScript.getActivationDateTime().toZonedDateTime();
+    }
+
+    @Override
+    public InputStream getActive(User user) throws ScriptNotFoundException, StorageException
{
+        Optional<JPASieveScript> script = findActiveSieveScript(user);
+        JPASieveScript activeSieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable
to find active script for user " + user.asString()));
+        return IOUtils.toInputStream(activeSieveScript.getScriptContent(), StandardCharsets.UTF_8);
+    }
+
+    private Optional<JPASieveScript> findActiveSieveScript(User user) throws StorageException
{
+        return transactionRunner.runAndRetrieveResult(
+                Throwing.<EntityManager, Optional<JPASieveScript>>function(entityManager
-> findActiveSieveScript(user, entityManager)).sneakyThrow(),
+                throwStorageException("Unable to find active script for user " + user.asString()));
+    }
+
+    private Optional<JPASieveScript> findActiveSieveScript(User user, EntityManager
entityManager) throws StorageException {
+        try {
+            JPASieveScript activeSieveScript = entityManager.createNamedQuery("findActiveByUsername",
JPASieveScript.class)
+                    .setParameter("username", user.asString()).getSingleResult();
+            return Optional.ofNullable(activeSieveScript);
+        } catch (NoResultException e) {
+            LOGGER.debug("Sieve script not found for user {}", user.asString());
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public void setActive(User user, ScriptName name) throws ScriptNotFoundException, StorageException
{
+        transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager
-> {
+            try {
+                if (SieveRepository.NO_SCRIPT_NAME.equals(name)) {
+                    switchOffActiveScript(user, entityManager);
+                } else {
+                    setActiveScript(user, name, entityManager);
+                }
+            } catch (StorageException | ScriptNotFoundException e) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw e;
+            }
+        }).sneakyThrow(), throwStorageException("Unable to set active script " + name.getValue()
+ " for user " + user.asString()));
+    }
+
+    private void switchOffActiveScript(User user, EntityManager entityManager) throws StorageException
{
+        Optional<JPASieveScript> activeSieveScript = findActiveSieveScript(user, entityManager);
+        activeSieveScript.ifPresent(JPASieveScript::deactivate);
+    }
+
+    private void setActiveScript(User user, ScriptName name, EntityManager entityManager)
throws StorageException, ScriptNotFoundException {
+        JPASieveScript sieveScript = findSieveScript(user, name, entityManager)
+                .orElseThrow(() -> new ScriptNotFoundException("Unable to find script
" + name.getValue() + " for user " + user.asString()));
+        findActiveSieveScript(user, entityManager).ifPresent(JPASieveScript::deactivate);
+        sieveScript.activate();
+    }
+
+    @Override
+    public InputStream getScript(User user, ScriptName name) throws ScriptNotFoundException,
StorageException {
+        Optional<JPASieveScript> script = findSieveScript(user, name);
+        JPASieveScript sieveScript = script.orElseThrow(() -> new ScriptNotFoundException("Unable
to find script " + name.getValue() + " for user " + user.asString()));
+        return IOUtils.toInputStream(sieveScript.getScriptContent(), StandardCharsets.UTF_8);
+    }
+
+    private Optional<JPASieveScript> findSieveScript(User user, ScriptName scriptName)
throws StorageException {
+        return transactionRunner.runAndRetrieveResult(entityManager -> findSieveScript(user,
scriptName, entityManager),
+                throwStorageException("Unable to find script " + scriptName.getValue() +
" for user " + user.asString()));
+    }
+
+    private Optional<JPASieveScript> findSieveScript(User user, ScriptName scriptName,
EntityManager entityManager) {
+        try {
+            JPASieveScript sieveScript = entityManager.createNamedQuery("findSieveScript",
JPASieveScript.class)
+                    .setParameter("username", user.asString())
+                    .setParameter("scriptName", scriptName.getValue()).getSingleResult();
+            return Optional.ofNullable(sieveScript);
+        } catch (NoResultException e) {
+            LOGGER.debug("Sieve script not found for user {}", user.asString());
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public void deleteScript(User user, ScriptName name) throws ScriptNotFoundException,
IsActiveException, StorageException {
+        transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager
-> {
+            Optional<JPASieveScript> sieveScript = findSieveScript(user, name, entityManager);
+            if (!sieveScript.isPresent()) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw new ScriptNotFoundException("Unable to find script " + name.getValue()
+ " for user " + user.asString());
+            }
+            JPASieveScript sieveScriptToRemove = sieveScript.get();
+            if (sieveScriptToRemove.isActive()) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw new IsActiveException("Unable to delete active script " + name.getValue()
+ " for user " + user.asString());
+            }
+            entityManager.remove(sieveScriptToRemove);
+        }).sneakyThrow(), throwStorageException("Unable to delete script " + name.getValue()
+ " for user " + user.asString()));
+    }
+
+    @Override
+    public void renameScript(User user, ScriptName oldName, ScriptName newName) throws ScriptNotFoundException,
DuplicateException, StorageException {
+        transactionRunner.runAndHandleException(Throwing.<EntityManager>consumer(entityManager
-> {
+            Optional<JPASieveScript> sieveScript = findSieveScript(user, oldName, entityManager);
+            if (!sieveScript.isPresent()) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw new ScriptNotFoundException("Unable to find script " + oldName.getValue()
+ " for user " + user.asString());
+            }
+
+            Optional<JPASieveScript> duplicatedSieveScript = findSieveScript(user,
newName, entityManager);
+            if (duplicatedSieveScript.isPresent()) {
+                rollbackTransactionIfActive(entityManager.getTransaction());
+                throw new DuplicateException("Unable to rename script. Duplicate found "
+ newName.getValue() + " for user " + user.asString());
+            }
+
+            JPASieveScript sieveScriptToRename = sieveScript.get();
+            sieveScriptToRename.renameTo(newName);
+        }).sneakyThrow(), throwStorageException("Unable to rename script " + oldName.getValue()
+ " for user " + user.asString()));
+    }
+
+    private void rollbackTransactionIfActive(EntityTransaction transaction) {
+        if (transaction.isActive()) {
+            transaction.rollback();
+        }
+    }
+
+    @Override
+    public boolean hasDefaultQuota() throws StorageException {
+        Optional<JPASieveQuota> defaultQuota = findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME);
+        return defaultQuota.isPresent();
+    }
+
+    @Override
+    public QuotaSize getDefaultQuota() throws QuotaNotFoundException, StorageException {
+        JPASieveQuota jpaSieveQuota = findQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME)
+                .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for
default user"));
+        return QuotaSize.size(jpaSieveQuota.getSize());
+    }
+
+    @Override
+    public void setDefaultQuota(QuotaSize quota) throws StorageException {
+        setQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME, quota);
+    }
+
+    @Override
+    public void removeQuota() throws QuotaNotFoundException, StorageException {
+        removeQuotaForUser(DEFAULT_SIEVE_QUOTA_USERNAME);
+    }
+
+    @Override
+    public boolean hasQuota(User user) throws StorageException {
+        Optional<JPASieveQuota> quotaForUser = findQuotaForUser(user.asString());
+        return quotaForUser.isPresent();
+    }
+
+    @Override
+    public QuotaSize getQuota(User user) throws QuotaNotFoundException, StorageException
{
+        JPASieveQuota jpaSieveQuota = findQuotaForUser(user.asString())
+                .orElseThrow(() -> new QuotaNotFoundException("Unable to find quota for
user " + user.asString()));
+        return QuotaSize.size(jpaSieveQuota.getSize());
+    }
+
+    @Override
+    public void setQuota(User user, QuotaSize quota) throws StorageException {
+        setQuotaForUser(user.asString(), quota);
+    }
+
+    @Override
+    public void removeQuota(User user) throws QuotaNotFoundException, StorageException {
+        removeQuotaForUser(user.asString());
+    }
+
+    private Optional<JPASieveQuota> findQuotaForUser(String username) throws StorageException
{
+        return transactionRunner.runAndRetrieveResult(entityManager -> findQuotaForUser(username,
entityManager),
+                throwStorageException("Unable to find quota for user " + username));
+    }
+
+    private <T> Function<PersistenceException, T> throwStorageException(String
message) {
+        return Throwing.<PersistenceException, T>function(e -> {
+            throw new StorageException(message, e);
+        }).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, QuotaSize 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);
+            }
+        }), throwStorageException("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);
+        }), throwStorageException("Unable to remove quota for user " + username));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java
b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java
new file mode 100644
index 0000000..9c5e097
--- /dev/null
+++ b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveQuota.java
@@ -0,0 +1,97 @@
+/****************************************************************
+ * 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.QuotaSize;
+
+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(QuotaSize quotaSize) {
+        this.size = quotaSize.asLong();
+    }
+
+    public QuotaSize toQuotaSize() {
+        return QuotaSize.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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java
b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java
new file mode 100644
index 0000000..3f846ec
--- /dev/null
+++ b/server/data/data-jpa/src/main/java/org/apache/james/sieve/jpa/model/JPASieveScript.java
@@ -0,0 +1,200 @@
+/****************************************************************
+ * 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.time.OffsetDateTime;
+import java.util.Objects;
+import java.util.UUID;
+
+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.commons.lang.StringUtils;
+import org.apache.james.sieverepository.api.ScriptContent;
+import org.apache.james.sieverepository.api.ScriptName;
+import org.apache.james.sieverepository.api.ScriptSummary;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+@Entity(name = "JamesSieveScript")
+@Table(name = "JAMES_SIEVE_SCRIPT")
+@NamedQueries({
+        @NamedQuery(name = "findAllByUsername", query = "SELECT sieveScript FROM JamesSieveScript
sieveScript WHERE sieveScript.username=:username"),
+        @NamedQuery(name = "findActiveByUsername", query = "SELECT sieveScript FROM JamesSieveScript
sieveScript WHERE sieveScript.username=:username AND sieveScript.isActive=true"),
+        @NamedQuery(name = "findSieveScript", query = "SELECT sieveScript FROM JamesSieveScript
sieveScript WHERE sieveScript.username=:username AND sieveScript.scriptName=:scriptName")
+})
+public class JPASieveScript {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static ScriptSummary toSummary(JPASieveScript script) {
+        return new ScriptSummary(new ScriptName(script.getScriptName()), script.isActive());
+    }
+
+    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(ScriptContent scriptContent) {
+            Preconditions.checkNotNull(scriptContent);
+            this.scriptContent = scriptContent.getValue();
+            this.scriptSize = scriptContent.length();
+            return this;
+        }
+
+        public Builder isActive(boolean isActive) {
+            this.isActive = isActive;
+            return this;
+        }
+
+        public JPASieveScript build() {
+            Preconditions.checkState(StringUtils.isNotBlank(username), "'username' is mandatory");
+            Preconditions.checkState(StringUtils.isNotBlank(scriptName), "'scriptName' is
mandatory");
+            this.activationDateTime = isActive ? OffsetDateTime.now() : null;
+            return new JPASieveScript(username, scriptName, scriptContent, scriptSize, isActive,
activationDateTime);
+        }
+    }
+
+    @Id
+    private String uuid = UUID.randomUUID().toString();
+
+    @Column(name = "USER_NAME", nullable = false, length = 100)
+    private String username;
+
+    @Column(name = "SCRIPT_NAME", nullable = false, length = 255)
+    private String scriptName;
+
+    @Column(name = "SCRIPT_CONTENT", nullable = false, length = 1024)
+    private String scriptContent;
+
+    @Column(name = "SCRIPT_SIZE", nullable = false)
+    private long scriptSize;
+
+    @Column(name = "IS_ACTIVE", nullable = false)
+    private boolean isActive;
+
+    @Column(name = "ACTIVATION_DATE_TIME")
+    private OffsetDateTime activationDateTime;
+
+    /**
+     * @deprecated enhancement only
+     */
+    @Deprecated
+    protected JPASieveScript() {
+    }
+
+    private JPASieveScript(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 void activate() {
+        this.isActive = true;
+        this.activationDateTime = OffsetDateTime.now();
+    }
+
+    public void deactivate() {
+        this.isActive = false;
+        this.activationDateTime = null;
+    }
+
+    public void renameTo(ScriptName newName) {
+        this.scriptName = newName.getValue();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        JPASieveScript that = (JPASieveScript) o;
+        return Objects.equals(uuid, that.uuid);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(uuid);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("uuid", uuid)
+                .add("username", username)
+                .add("scriptName", scriptName)
+                .add("isActive", isActive)
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/src/main/resources/META-INF/persistence.xml b/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
index 0e43be9..1927b87 100644
--- a/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
+++ b/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
@@ -30,6 +30,8 @@
         <class>org.apache.james.user.jpa.model.JPAUser</class>
         <class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
         <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveScript</class>
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
         <properties>
             <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-jpa/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java
b/server/data/data-jpa/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java
new file mode 100644
index 0000000..660515c
--- /dev/null
+++ b/server/data/data-jpa/src/test/java/org/apache/james/sieve/jpa/JpaSieveRepositoryTest.java
@@ -0,0 +1,50 @@
+/****************************************************************
+ * 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;
+
+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.sieverepository.api.SieveRepository;
+import org.apache.james.sieverepository.lib.AbstractSieveRepositoryTest;
+
+import org.junit.After;
+import org.junit.Before;
+
+public class JpaSieveRepositoryTest extends AbstractSieveRepositoryTest {
+
+    private static final JpaTestCluster JPA_TEST_CLUSTER = JpaTestCluster.create(JPASieveScript.class,
JPASieveQuota.class);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        JPA_TEST_CLUSTER.clear("JAMES_SIEVE_SCRIPT", "JAMES_SIEVE_QUOTA");
+    }
+
+    @Override
+    protected SieveRepository createSieveRepository() throws Exception {
+        return new JPASieveRepository(JPA_TEST_CLUSTER.getEntityManagerFactory());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d5d5c9cb/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/AbstractSieveRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/AbstractSieveRepositoryTest.java
b/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/AbstractSieveRepositoryTest.java
index 266d8b8..eaf9746 100644
--- a/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/AbstractSieveRepositoryTest.java
+++ b/server/data/data-library/src/test/java/org/apache/james/sieverepository/lib/AbstractSieveRepositoryTest.java
@@ -345,6 +345,13 @@ public abstract class AbstractSieveRepositoryTest {
         assertThat(sieveRepository.getQuota(USER)).isEqualTo(DEFAULT_QUOTA);
     }
 
+    @Test
+    public void setQuotaShouldOverrideExistingQuota() throws Exception {
+        sieveRepository.setQuota(USER, USER_QUOTA);
+        sieveRepository.setQuota(USER, QuotaSize.size(USER_QUOTA.asLong() - 1));
+        assertThat(sieveRepository.getQuota(USER)).isEqualTo(QuotaSize.size(USER_QUOTA.asLong()
- 1));
+    }
+
     protected ScriptContent getScriptContent(InputStream inputStream) throws IOException
{
         return new ScriptContent(IOUtils.toString(inputStream, StandardCharsets.UTF_8));
     }


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


Mime
View raw message