JAMES-1617 Implement CassandraSieveRepository
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/96dfc5d5 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/96dfc5d5 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/96dfc5d5 Branch: refs/heads/master Commit: 96dfc5d59a634b9a478bd312e1f1b87fdbd4ba95 Parents: 5f995ab Author: Benoit Tellier <btell...@apache.org> Authored: Thu Feb 18 10:55:36 2016 +0700 Committer: Benoit Tellier <btell...@linagora.com> Committed: Thu Apr 7 09:13:30 2016 +0700 ---------------------------------------------------------------------- backends-common/cassandra/pom.xml | 5 + .../cassandra/utils/CassandraAsyncExecutor.java | 66 ++++ .../cassandra/utils/CassandraUtils.java | 5 +- .../sieverepository/api/SieveRepository.java | 2 + .../sieve/cassandra/CassandraSieveDAO.java | 316 +++++++++++++++++++ .../cassandra/CassandraSieveRepository.java | 240 ++++++++++++++ .../CassandraSieveRepositoryModule.java | 97 ++++++ .../model/ScriptContentAndActivation.java | 39 +++ .../james/sieve/cassandra/model/SieveQuota.java | 51 +++ .../tables/CassandraSieveClusterQuotaTable.java | 29 ++ .../tables/CassandraSieveQuotaTable.java | 27 ++ .../tables/CassandraSieveSpaceTable.java | 27 ++ .../cassandra/tables/CassandraSieveTable.java | 31 ++ .../cassandra/CassandraSieveRepositoryTest.java | 82 +++++ .../sieve/cassandra/model/SieveQuotaTest.java | 59 ++++ .../lib/AbstractSieveRepositoryTest.java | 28 +- 16 files changed, 1096 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/backends-common/cassandra/pom.xml ---------------------------------------------------------------------- diff --git a/backends-common/cassandra/pom.xml b/backends-common/cassandra/pom.xml index b74080b..d8d158a 100644 --- a/backends-common/cassandra/pom.xml +++ b/backends-common/cassandra/pom.xml @@ -149,6 +149,11 @@ <artifactId>javax.inject</artifactId> </dependency> <dependency> + <groupId>net.javacrumbs.future-converter</groupId> + <artifactId>future-converter-java8-guava</artifactId> + <version>0.3.0</version> + </dependency> + <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>${assertj-3.version}</version> http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraAsyncExecutor.java ---------------------------------------------------------------------- diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraAsyncExecutor.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraAsyncExecutor.java new file mode 100644 index 0000000..4559b67 --- /dev/null +++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraAsyncExecutor.java @@ -0,0 +1,66 @@ +/**************************************************************** + * 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.backends.cassandra.utils; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import javax.inject.Inject; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; + +import net.javacrumbs.futureconverter.java8guava.FutureConverter; + +public class CassandraAsyncExecutor { + + private final Session session; + + @Inject + public CassandraAsyncExecutor(Session session) { + this.session = session; + } + + public CompletableFuture<ResultSet> execute(Statement statement) { + return FutureConverter.toCompletableFuture(session.executeAsync(statement)); + } + + + public CompletableFuture<Boolean> executeReturnApplied(Statement statement) { + return FutureConverter.toCompletableFuture(session.executeAsync(statement)) + .thenApply(ResultSet::one) + .thenApply(row -> row.getBool(CassandraConstants.LIGHTWEIGHT_TRANSACTION_APPLIED)); + } + + public CompletableFuture<Void> executeVoid(Statement statement) { + return FutureConverter.toCompletableFuture(session.executeAsync(statement)) + .thenAccept(this::consume); + } + + public CompletableFuture<Optional<Row>> executeSingleRow(Statement statement) { + return execute(statement) + .thenApply(ResultSet::one) + .thenApply(Optional::ofNullable); + } + + private <U> void consume(U value) {} +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraUtils.java ---------------------------------------------------------------------- diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraUtils.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraUtils.java index 40c44e4..a9916bc 100644 --- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraUtils.java +++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraUtils.java @@ -19,11 +19,12 @@ package org.apache.james.backends.cassandra.utils; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.Row; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; + public class CassandraUtils { public static Stream<Row> convertToStream(ResultSet resultSet) { http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-api/src/main/java/org/apache/james/sieverepository/api/SieveRepository.java ---------------------------------------------------------------------- diff --git a/server/data/data-api/src/main/java/org/apache/james/sieverepository/api/SieveRepository.java b/server/data/data-api/src/main/java/org/apache/james/sieverepository/api/SieveRepository.java index 8e0a999..9bdb55e 100644 --- a/server/data/data-api/src/main/java/org/apache/james/sieverepository/api/SieveRepository.java +++ b/server/data/data-api/src/main/java/org/apache/james/sieverepository/api/SieveRepository.java @@ -37,6 +37,8 @@ import org.joda.time.DateTime; */ public interface SieveRepository { + String NO_SCRIPT_NAME = ""; + void haveSpace(String user, String name, long size) throws QuotaExceededException, StorageException; /** http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveDAO.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveDAO.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveDAO.java new file mode 100644 index 0000000..3ba2810 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveDAO.java @@ -0,0 +1,316 @@ +/**************************************************************** + * 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.cassandra; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.driver.core.querybuilder.QueryBuilder.delete; +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.incr; +import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; +import static com.datastax.driver.core.querybuilder.QueryBuilder.set; +import static com.datastax.driver.core.querybuilder.QueryBuilder.update; + +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; +import org.apache.james.sieve.cassandra.model.ScriptContentAndActivation; +import org.apache.james.sieve.cassandra.tables.CassandraSieveClusterQuotaTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveQuotaTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveSpaceTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveTable; +import org.apache.james.sieverepository.api.ScriptSummary; +import org.joda.time.DateTime; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.querybuilder.Select; + +public class CassandraSieveDAO { + + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final PreparedStatement insertScriptStatement; + private final PreparedStatement selectActiveScriptStatement; + private final PreparedStatement selectActiveScriptMetadataStatement; + private final PreparedStatement selectActiveScriptNameStatement; + private final PreparedStatement selectClusterQuotaStatement; + private final PreparedStatement selectScriptsStatement; + private final PreparedStatement selectScriptStatement; + private final PreparedStatement selectScriptMetadataStatement; + private final PreparedStatement selectSpaceUsedByUserStatement; + private final PreparedStatement selectUserQuotaStatement; + private final PreparedStatement updateClusterQuotaStatement; + private final PreparedStatement updateUserQuotaStatement; + private final PreparedStatement updateScriptActivationStatement; + private final PreparedStatement updateSpaceUsedStatement; + private final PreparedStatement deleteClusterQuotaStatement; + private final PreparedStatement deleteScriptStatement; + private final PreparedStatement deleteUserQuotaStatement; + + @Inject + public CassandraSieveDAO(Session session) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + + insertScriptStatement = session.prepare( + insertInto(CassandraSieveTable.TABLE_NAME) + .value(CassandraSieveTable.USER_NAME, bindMarker(CassandraSieveTable.USER_NAME)) + .value(CassandraSieveTable.SCRIPT_NAME, bindMarker(CassandraSieveTable.SCRIPT_NAME)) + .value(CassandraSieveTable.SCRIPT_CONTENT, bindMarker(CassandraSieveTable.SCRIPT_CONTENT)) + .value(CassandraSieveTable.IS_ACTIVE, bindMarker(CassandraSieveTable.IS_ACTIVE)) + .value(CassandraSieveTable.SIZE, bindMarker(CassandraSieveTable.SIZE)) + .value(CassandraSieveTable.DATE, bindMarker(CassandraSieveTable.DATE))); + + selectActiveScriptStatement = session.prepare(getScriptQuery(CassandraSieveTable.SCRIPT_CONTENT, CassandraSieveTable.DATE) + .and(eq(CassandraSieveTable.IS_ACTIVE, bindMarker(CassandraSieveTable.IS_ACTIVE)))); + + selectActiveScriptMetadataStatement = session.prepare(getScriptQuery(CassandraSieveTable.DATE) + .and(eq(CassandraSieveTable.IS_ACTIVE, bindMarker(CassandraSieveTable.IS_ACTIVE)))); + + selectActiveScriptNameStatement = session.prepare(getScriptQuery(CassandraSieveTable.SCRIPT_NAME) + .and(eq(CassandraSieveTable.IS_ACTIVE, bindMarker(CassandraSieveTable.IS_ACTIVE)))); + + selectClusterQuotaStatement = session.prepare( + select(CassandraSieveClusterQuotaTable.VALUE) + .from(CassandraSieveClusterQuotaTable.TABLE_NAME) + .where(eq(CassandraSieveClusterQuotaTable.NAME, bindMarker(CassandraSieveClusterQuotaTable.NAME)))); + + selectScriptsStatement = session.prepare( + select() + .from(CassandraSieveTable.TABLE_NAME) + .where(eq(CassandraSieveTable.USER_NAME, bindMarker(CassandraSieveTable.USER_NAME)))); + + selectScriptStatement = session.prepare(getScriptQuery(CassandraSieveTable.SCRIPT_CONTENT, CassandraSieveTable.IS_ACTIVE) + .and(eq(CassandraSieveTable.SCRIPT_NAME, bindMarker(CassandraSieveTable.SCRIPT_NAME)))); + + selectScriptMetadataStatement = session.prepare(getScriptQuery(CassandraSieveTable.SIZE, CassandraSieveTable.IS_ACTIVE, CassandraSieveTable.DATE) + .and(eq(CassandraSieveTable.SCRIPT_NAME, bindMarker(CassandraSieveTable.SCRIPT_NAME)))); + + selectSpaceUsedByUserStatement = session.prepare( + select(CassandraSieveSpaceTable.SPACE_USED) + .from(CassandraSieveSpaceTable.TABLE_NAME) + .where(eq(CassandraSieveSpaceTable.USER_NAME, bindMarker(CassandraSieveSpaceTable.USER_NAME)))); + + selectUserQuotaStatement = session.prepare( + select(CassandraSieveQuotaTable.QUOTA) + .from(CassandraSieveQuotaTable.TABLE_NAME) + .where(eq(CassandraSieveQuotaTable.USER_NAME, bindMarker(CassandraSieveQuotaTable.USER_NAME)))); + + updateClusterQuotaStatement = session.prepare( + update(CassandraSieveClusterQuotaTable.TABLE_NAME) + .with(set(CassandraSieveClusterQuotaTable.VALUE, bindMarker(CassandraSieveClusterQuotaTable.VALUE))) + .where(eq(CassandraSieveClusterQuotaTable.NAME, bindMarker(CassandraSieveClusterQuotaTable.NAME)))); + + updateScriptActivationStatement = session.prepare( + update(CassandraSieveTable.TABLE_NAME) + .with(set(CassandraSieveTable.IS_ACTIVE, bindMarker(CassandraSieveTable.IS_ACTIVE))) + .where(eq(CassandraSieveTable.USER_NAME, bindMarker(CassandraSieveTable.USER_NAME))) + .and(eq(CassandraSieveTable.SCRIPT_NAME, bindMarker(CassandraSieveTable.SCRIPT_NAME))) + .ifExists()); + + updateSpaceUsedStatement = session.prepare( + update(CassandraSieveSpaceTable.TABLE_NAME) + .with(incr(CassandraSieveSpaceTable.SPACE_USED, bindMarker(CassandraSieveSpaceTable.SPACE_USED))) + .where(eq(CassandraSieveSpaceTable.USER_NAME, bindMarker(CassandraSieveSpaceTable.USER_NAME)))); + + updateUserQuotaStatement = session.prepare( + update(CassandraSieveQuotaTable.TABLE_NAME) + .with(set(CassandraSieveQuotaTable.QUOTA, bindMarker(CassandraSieveQuotaTable.QUOTA))) + .where(eq(CassandraSieveQuotaTable.USER_NAME, bindMarker(CassandraSieveQuotaTable.USER_NAME)))); + + deleteScriptStatement = session.prepare( + delete() + .from(CassandraSieveTable.TABLE_NAME) + .where(eq(CassandraSieveTable.USER_NAME, bindMarker(CassandraSieveTable.USER_NAME))) + .and(eq(CassandraSieveTable.SCRIPT_NAME, bindMarker(CassandraSieveTable.SCRIPT_NAME))) + .ifExists()); + + deleteClusterQuotaStatement = session.prepare( + delete() + .from(CassandraSieveClusterQuotaTable.TABLE_NAME) + .where(eq(CassandraSieveClusterQuotaTable.NAME, bindMarker(CassandraSieveClusterQuotaTable.NAME))) + .ifExists()); + + deleteUserQuotaStatement = session.prepare( + delete() + .from(CassandraSieveQuotaTable.TABLE_NAME) + .where(eq(CassandraSieveQuotaTable.USER_NAME, bindMarker(CassandraSieveQuotaTable.USER_NAME))) + .ifExists()); + } + + private Select.Where getScriptQuery(String... selectedRows) { + return select(selectedRows) + .from(CassandraSieveTable.TABLE_NAME) + .where(eq(CassandraSieveTable.USER_NAME, bindMarker(CassandraSieveTable.USER_NAME))); + } + + public CompletableFuture<Long> spaceUsedBy(String user) { + return cassandraAsyncExecutor.executeSingleRow( + selectSpaceUsedByUserStatement.bind() + .setString(CassandraSieveSpaceTable.USER_NAME, user)) + .thenApply(optional -> optional.map(row -> row.getLong(CassandraSieveSpaceTable.SPACE_USED)) + .orElse(0L)); + } + + public CompletableFuture<Void> insertScript(String user, String name, String content, boolean isActive) { + return cassandraAsyncExecutor.executeVoid( + insertScriptStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setString(CassandraSieveTable.SCRIPT_NAME, name) + .setString(CassandraSieveTable.SCRIPT_CONTENT, content) + .setBool(CassandraSieveTable.IS_ACTIVE, isActive) + .setLong(CassandraSieveTable.SIZE, content.getBytes().length) + .setDate(CassandraSieveTable.DATE, new Date())); + } + + public CompletableFuture<List<ScriptSummary>> listScripts(String user) { + return cassandraAsyncExecutor.execute( + selectScriptsStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user)) + .thenApply(resultSet -> resultSet.all() + .stream() + .map(row -> new ScriptSummary( + row.getString(CassandraSieveTable.SCRIPT_NAME), + row.getBool(CassandraSieveTable.IS_ACTIVE))) + .collect(Collectors.toList())); + } + + public CompletableFuture<Void> updateSpaceUsed(String user, long spaceUsed) { + return cassandraAsyncExecutor.executeVoid( + updateSpaceUsedStatement.bind() + .setLong(CassandraSieveSpaceTable.SPACE_USED, spaceUsed) + .setString(CassandraSieveSpaceTable.USER_NAME, user)); + } + + public CompletableFuture<Boolean> updateScriptActivation(String user, String scriptName, boolean active) { + return cassandraAsyncExecutor.executeReturnApplied( + updateScriptActivationStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setString(CassandraSieveTable.SCRIPT_NAME, scriptName) + .setBool(CassandraSieveTable.IS_ACTIVE, active)); + } + + public CompletableFuture<Optional<ScriptContentAndActivation>> getScriptContentAndActivation(String user, String name) { + return getScriptRow(user, name).thenApply(opt -> opt.map(row -> new ScriptContentAndActivation( + row.getString(CassandraSieveTable.SCRIPT_CONTENT), + row.getBool(CassandraSieveTable.IS_ACTIVE)))); + } + + public CompletableFuture<Optional<String>> getScriptContent(String user, String name) { + return getScriptRow(user, name).thenApply(opt -> opt.map(row -> row.getString(CassandraSieveTable.SCRIPT_CONTENT))); + } + + public CompletableFuture<Optional<Long>> getScriptSize(String user, String name) { + return cassandraAsyncExecutor.executeSingleRow( + selectScriptMetadataStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setString(CassandraSieveTable.SCRIPT_NAME, name)) + .thenApply(rowOptional -> rowOptional.map(row -> row.getLong(CassandraSieveTable.SIZE))); + } + + public CompletableFuture<Optional<DateTime>> getActiveScriptActivationDate(String user) { + return cassandraAsyncExecutor.executeSingleRow( + selectActiveScriptMetadataStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setBool(CassandraSieveTable.IS_ACTIVE, true)) + .thenApply(rowOptional -> rowOptional.map(row -> new DateTime(row.getDate(CassandraSieveTable.DATE).getTime()))); + } + + public CompletableFuture<Boolean> deleteScriptInCassandra(String user, String name) { + return cassandraAsyncExecutor.executeReturnApplied( + deleteScriptStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setString(CassandraSieveTable.SCRIPT_NAME, name)); + } + + public CompletableFuture<Boolean> scriptExists(String user, String name) { + return getScriptSize(user, name).thenApply(Optional::isPresent); + } + + public CompletableFuture<Optional<Long>> getQuota() { + return cassandraAsyncExecutor.executeSingleRow( + selectClusterQuotaStatement.bind() + .setString(CassandraSieveClusterQuotaTable.NAME, CassandraSieveClusterQuotaTable.DEFAULT_NAME)) + .thenApply(optional -> optional.map(row -> row.getLong(CassandraSieveClusterQuotaTable.VALUE))); + } + + public CompletableFuture<Void> setQuota(long quota) { + return cassandraAsyncExecutor.executeVoid( + updateClusterQuotaStatement.bind() + .setLong(CassandraSieveClusterQuotaTable.VALUE, quota) + .setString(CassandraSieveClusterQuotaTable.NAME, CassandraSieveClusterQuotaTable.DEFAULT_NAME)); + } + + public CompletableFuture<Boolean> removeQuota() { + return cassandraAsyncExecutor.executeReturnApplied( + deleteClusterQuotaStatement.bind() + .setString(CassandraSieveClusterQuotaTable.NAME, CassandraSieveClusterQuotaTable.DEFAULT_NAME)); + } + + public CompletableFuture<Optional<Long>> getQuota(String user) { + return cassandraAsyncExecutor.executeSingleRow( + selectUserQuotaStatement.bind() + .setString(CassandraSieveQuotaTable.USER_NAME, user)) + .thenApply(optional -> optional.map(row -> row.getLong(CassandraSieveQuotaTable.QUOTA))); + } + + public CompletableFuture<Optional<String>> getActiveName(String user) { + return cassandraAsyncExecutor.executeSingleRow( + selectActiveScriptNameStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setBool(CassandraSieveTable.IS_ACTIVE, true)) + .thenApply(optional -> optional.map(row -> row.getString(CassandraSieveTable.SCRIPT_NAME))); + } + + public CompletableFuture<Void> setQuota(String user, long quota) { + return cassandraAsyncExecutor.executeVoid( + updateUserQuotaStatement.bind() + .setLong(CassandraSieveQuotaTable.QUOTA, quota) + .setString(CassandraSieveQuotaTable.USER_NAME, user)); + } + + public CompletableFuture<Boolean> removeQuota(String user) { + return cassandraAsyncExecutor.executeReturnApplied( + deleteUserQuotaStatement.bind() + .setString(CassandraSieveQuotaTable.USER_NAME, user)); + } + + public CompletableFuture<Optional<String>> getActive(String user) { + return cassandraAsyncExecutor.executeSingleRow( + selectActiveScriptStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setBool(CassandraSieveTable.IS_ACTIVE, true)) + .thenApply(rowOptional -> rowOptional.map(row -> row.getString(CassandraSieveTable.SCRIPT_CONTENT))); + } + + private CompletableFuture<Optional<Row>> getScriptRow(String user, String name) { + return cassandraAsyncExecutor.executeSingleRow( + selectScriptStatement.bind() + .setString(CassandraSieveTable.USER_NAME, user) + .setString(CassandraSieveTable.SCRIPT_NAME, name)); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepository.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepository.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepository.java new file mode 100644 index 0000000..2a36a91 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepository.java @@ -0,0 +1,240 @@ +/**************************************************************** + * 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.cassandra; + +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import javax.inject.Inject; + +import org.apache.commons.io.IOUtils; +import org.apache.james.sieve.cassandra.model.ScriptContentAndActivation; +import org.apache.james.sieverepository.api.ScriptSummary; +import org.apache.james.sieve.cassandra.model.SieveQuota; +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.joda.time.DateTime; + +public class CassandraSieveRepository implements SieveRepository { + + private final CassandraSieveDAO cassandraSieveDAO; + + @Inject + public CassandraSieveRepository(CassandraSieveDAO cassandraSieveDAO) { + this.cassandraSieveDAO = cassandraSieveDAO; + } + + @Override + public DateTime getActivationDateForActiveScript(String user) throws StorageException, ScriptNotFoundException { + return cassandraSieveDAO.getActiveScriptActivationDate(user) + .join() + .orElseThrow(ScriptNotFoundException::new); + } + + @Override + public void haveSpace(String user, String name, long newSize) throws QuotaExceededException, StorageException { + throwOnOverQuota(user, spaceThatWillBeUsedByNewScript(user, name, newSize)); + } + + private void throwOnOverQuota(String user, CompletableFuture<Long> sizeDifference) throws QuotaExceededException, StorageException { + CompletableFuture<Optional<Long>> userQuotaFuture = cassandraSieveDAO.getQuota(user); + CompletableFuture<Optional<Long>> globalQuotaFuture = cassandraSieveDAO.getQuota(); + CompletableFuture<Long> spaceUsedFuture = cassandraSieveDAO.spaceUsedBy(user); + + new SieveQuota(spaceUsedFuture.join(), limitToUse(userQuotaFuture, globalQuotaFuture)).checkOverQuotaUponModification(sizeDifference.join()); + } + + public CompletableFuture<Long> spaceThatWillBeUsedByNewScript(String user, String name, long scriptSize) { + return cassandraSieveDAO.getScriptSize(user, name) + .thenApply(sizeOfStoredScript -> scriptSize - sizeOfStoredScript.orElse(0L)); + } + + private Optional<Long> limitToUse(CompletableFuture<Optional<Long>> userQuota, CompletableFuture<Optional<Long>> globalQuota) { + if (userQuota.join().isPresent()) { + return userQuota.join(); + } + return globalQuota.join(); + } + + @Override + public void putScript(String user, String name, String content) throws QuotaExceededException, StorageException { + CompletableFuture<Long> spaceUsed = spaceThatWillBeUsedByNewScript(user, name, content.length()); + throwOnOverQuota(user, spaceUsed); + + CompletableFuture.allOf( + updateSpaceUsed(user, spaceUsed.join()), + cassandraSieveDAO.insertScript(user, name, content, false)) + .join(); + } + + public CompletableFuture<Void> updateSpaceUsed(String user, long spaceUsed) { + if (spaceUsed == 0) { + return CompletableFuture.completedFuture(null); + } + return cassandraSieveDAO.updateSpaceUsed(user, spaceUsed); + } + + @Override + public List<ScriptSummary> listScripts(String user) { + return cassandraSieveDAO.listScripts(user).join(); + } + + @Override + public InputStream getActive(String user) throws ScriptNotFoundException { + return IOUtils.toInputStream( + cassandraSieveDAO.getActive(user) + .join() + .orElseThrow(ScriptNotFoundException::new)); + } + + @Override + public void setActive(String user, String name) throws ScriptNotFoundException { + CompletableFuture<Void> unactivateOldScriptFuture = unactivateOldScript(user); + CompletableFuture<Boolean> activateNewScript = updateScriptActivation(user, name, true); + + unactivateOldScriptFuture.join(); + if (!activateNewScript.join()) { + throw new ScriptNotFoundException(); + } + } + + private CompletableFuture<Void> unactivateOldScript(String user) { + return cassandraSieveDAO.getActiveName(user) + .thenCompose(scriptNameOptional -> scriptNameOptional + .map(scriptName -> updateScriptActivation(user, scriptName, false) + .<Void>thenApply(any -> null)) + .orElse(CompletableFuture.completedFuture(null))); + } + + private CompletableFuture<Boolean> updateScriptActivation(String user, String scriptName, boolean active) { + if (!scriptName.equals(SieveRepository.NO_SCRIPT_NAME)) { + return cassandraSieveDAO.updateScriptActivation(user, scriptName, active); + } + return CompletableFuture.completedFuture(true); + } + + @Override + public InputStream getScript(String user, String name) throws ScriptNotFoundException { + return cassandraSieveDAO.getScriptContent(user, name) + .join() + .map(IOUtils::toInputStream) + .orElseThrow(ScriptNotFoundException::new); + } + + @Override + public void deleteScript(String user, String name) throws ScriptNotFoundException, IsActiveException { + ensureIsNotActive(user, name); + if (!cassandraSieveDAO.deleteScriptInCassandra(user, name).join()) { + throw new ScriptNotFoundException(); + } + } + + private void ensureIsNotActive(String user, String name) throws IsActiveException { + Optional<String> activeName = cassandraSieveDAO.getActiveName(user).join(); + if (activeName.isPresent() && name.equals(activeName.get())) { + throw new IsActiveException(); + } + } + + @Override + public void renameScript(String user, String oldName, String newName) throws ScriptNotFoundException, DuplicateException { + CompletableFuture<Boolean> scriptExistsFuture = cassandraSieveDAO.scriptExists(user, newName); + CompletableFuture<Optional<ScriptContentAndActivation>> oldScriptFuture = cassandraSieveDAO.getScriptContentAndActivation(user, oldName); + + oldScriptFuture.join(); + if (scriptExistsFuture.join()) { + throw new DuplicateException(); + } + + performScriptRename(user, + oldName, + newName, + oldScriptFuture.join().orElseThrow(ScriptNotFoundException::new)); + } + + private void performScriptRename(String user, String oldName, String newName, ScriptContentAndActivation oldScript) { + CompletableFuture.allOf( + cassandraSieveDAO.insertScript(user, newName, oldScript.getContent(), oldScript.isActive()), + cassandraSieveDAO.deleteScriptInCassandra(user, oldName)) + .join(); + } + + @Override + public boolean hasQuota() { + return cassandraSieveDAO.getQuota() + .join() + .isPresent(); + } + + @Override + public long getQuota() throws QuotaNotFoundException { + return cassandraSieveDAO.getQuota() + .join() + .orElseThrow(QuotaNotFoundException::new); + } + + @Override + public void setQuota(long quota) { + cassandraSieveDAO.setQuota(quota).join(); + } + + @Override + public void removeQuota() throws QuotaNotFoundException { + if (!cassandraSieveDAO.removeQuota().join()) { + throw new QuotaNotFoundException(); + } + } + + @Override + public boolean hasQuota(String user) { + CompletableFuture<Boolean> userQuotaIsPresent = cassandraSieveDAO.getQuota(user).thenApply(Optional::isPresent); + CompletableFuture<Boolean> globalQuotaIsPresent = cassandraSieveDAO.getQuota().thenApply(Optional::isPresent); + CompletableFuture.allOf(userQuotaIsPresent, globalQuotaIsPresent).join(); + + return userQuotaIsPresent.join() || globalQuotaIsPresent.join(); + } + + @Override + public long getQuota(String user) throws QuotaNotFoundException { + return cassandraSieveDAO.getQuota(user) + .join() + .orElseThrow(QuotaNotFoundException::new); + } + + @Override + public void setQuota(String user, long quota) { + cassandraSieveDAO.setQuota(user, quota).join(); + } + + @Override + public void removeQuota(String user) throws QuotaNotFoundException { + if (!cassandraSieveDAO.removeQuota(user).join()) { + throw new QuotaNotFoundException(); + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryModule.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryModule.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryModule.java new file mode 100644 index 0000000..c59053d --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryModule.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.cassandra; + +import static com.datastax.driver.core.DataType.bigint; +import static com.datastax.driver.core.DataType.cboolean; +import static com.datastax.driver.core.DataType.counter; +import static com.datastax.driver.core.DataType.text; +import static com.datastax.driver.core.DataType.timestamp; + +import java.util.List; + +import org.apache.james.backends.cassandra.components.CassandraIndex; +import org.apache.james.backends.cassandra.components.CassandraModule; +import org.apache.james.backends.cassandra.components.CassandraTable; +import org.apache.james.backends.cassandra.components.CassandraType; +import org.apache.james.sieve.cassandra.tables.CassandraSieveClusterQuotaTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveQuotaTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveSpaceTable; +import org.apache.james.sieve.cassandra.tables.CassandraSieveTable; + +import com.datastax.driver.core.schemabuilder.SchemaBuilder; +import com.google.common.collect.ImmutableList; + +public class CassandraSieveRepositoryModule implements CassandraModule { + + private final List<CassandraTable> tables; + private final List<CassandraIndex> index; + private final List<CassandraType> types; + + public CassandraSieveRepositoryModule() { + tables = ImmutableList.of( + new CassandraTable(CassandraSieveTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraSieveTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraSieveTable.USER_NAME, text()) + .addClusteringColumn(CassandraSieveTable.SCRIPT_NAME, text()) + .addColumn(CassandraSieveTable.SCRIPT_CONTENT, text()) + .addColumn(CassandraSieveTable.IS_ACTIVE, cboolean()) + .addColumn(CassandraSieveTable.DATE, timestamp()) + .addColumn(CassandraSieveTable.SIZE, bigint())), + new CassandraTable(CassandraSieveSpaceTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraSieveSpaceTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraSieveSpaceTable.USER_NAME, text()) + .addColumn(CassandraSieveSpaceTable.SPACE_USED, counter())), + new CassandraTable(CassandraSieveQuotaTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraSieveQuotaTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraSieveQuotaTable.USER_NAME, text()) + .addColumn(CassandraSieveQuotaTable.QUOTA, bigint())), + new CassandraTable(CassandraSieveClusterQuotaTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraSieveClusterQuotaTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraSieveClusterQuotaTable.NAME, text()) + .addColumn(CassandraSieveClusterQuotaTable.VALUE, bigint()))); + index = ImmutableList.of( + new CassandraIndex( + SchemaBuilder.createIndex(CassandraIndex.INDEX_PREFIX + CassandraSieveTable.TABLE_NAME + CassandraSieveTable.IS_ACTIVE) + .ifNotExists() + .onTable(CassandraSieveTable.TABLE_NAME) + .andColumn(CassandraSieveTable.IS_ACTIVE))); + types = ImmutableList.of(); + } + + @Override + public List<CassandraTable> moduleTables() { + return tables; + } + + @Override + public List<CassandraIndex> moduleIndex() { + return index; + } + + @Override + public List<CassandraType> moduleTypes() { + return types; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/ScriptContentAndActivation.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/ScriptContentAndActivation.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/ScriptContentAndActivation.java new file mode 100644 index 0000000..0e26b7b --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/ScriptContentAndActivation.java @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.cassandra.model; + +public class ScriptContentAndActivation { + + private final String content; + private final boolean activation; + + public ScriptContentAndActivation(String content, boolean activation) { + this.content = content; + this.activation = activation; + } + + public String getContent() { + return content; + } + + public boolean isActive() { + return activation; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/SieveQuota.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/SieveQuota.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/SieveQuota.java new file mode 100644 index 0000000..c1a44d6 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/model/SieveQuota.java @@ -0,0 +1,51 @@ +/* + * 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.cassandra.model; + +import java.util.Optional; + +import org.apache.james.sieverepository.api.exception.QuotaExceededException; + +import com.google.common.base.Preconditions; + +public class SieveQuota { + + private final long currentUsage; + private final Optional<Long> limit; + + public SieveQuota(long currentUsage, Optional<Long> limit) { + Preconditions.checkArgument(currentUsage >= 0, "Current usage should be positive or equal to zero"); + limit.ifPresent(limitValue -> Preconditions.checkArgument(limitValue >= 0, "Limit value should be positive or equal to zero")); + this.currentUsage = currentUsage; + this.limit = limit; + } + + public void checkOverQuotaUponModification(long sizeDifference) throws QuotaExceededException { + if (isExceededUponModification(sizeDifference)) { + throw new QuotaExceededException(); + } + } + + public boolean isExceededUponModification(long sizeDifference) { + return limit.map(limitContent -> currentUsage + sizeDifference > limitContent) + .orElse(false); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveClusterQuotaTable.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveClusterQuotaTable.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveClusterQuotaTable.java new file mode 100644 index 0000000..16beb0f --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveClusterQuotaTable.java @@ -0,0 +1,29 @@ +/**************************************************************** + * 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.cassandra.tables; + +public interface CassandraSieveClusterQuotaTable { + String TABLE_NAME = "sieve_cluster_quota"; + + String NAME = "name"; + String VALUE = "value"; + + String DEFAULT_NAME = "cluster_quota"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveQuotaTable.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveQuotaTable.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveQuotaTable.java new file mode 100644 index 0000000..94c6425 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveQuotaTable.java @@ -0,0 +1,27 @@ +/**************************************************************** + * 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.cassandra.tables; + +public interface CassandraSieveQuotaTable { + String TABLE_NAME = "sieve_quota"; + + String USER_NAME = "user_name"; + String QUOTA = "quota"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveSpaceTable.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveSpaceTable.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveSpaceTable.java new file mode 100644 index 0000000..8156e77 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveSpaceTable.java @@ -0,0 +1,27 @@ +/**************************************************************** + * 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.cassandra.tables; + +public interface CassandraSieveSpaceTable { + String TABLE_NAME = "sieve_space"; + + String USER_NAME = "user_name"; + String SPACE_USED = "space_used"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveTable.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveTable.java b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveTable.java new file mode 100644 index 0000000..9479ea3 --- /dev/null +++ b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveTable.java @@ -0,0 +1,31 @@ +/**************************************************************** + * 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.cassandra.tables; + +public interface CassandraSieveTable { + String TABLE_NAME = "sieve"; + + String USER_NAME = "user_name"; + String SCRIPT_NAME = "script_name"; + String SCRIPT_CONTENT = "script_content"; + String IS_ACTIVE = "is_active"; + String DATE = "date"; + String SIZE = "size"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryTest.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryTest.java b/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryTest.java new file mode 100644 index 0000000..14ff4e8 --- /dev/null +++ b/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/CassandraSieveRepositoryTest.java @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.cassandra; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Date; + +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; +import org.apache.james.sieve.cassandra.tables.CassandraSieveTable; +import org.apache.james.sieverepository.api.SieveRepository; +import org.apache.james.sieverepository.api.exception.ScriptNotFoundException; +import org.apache.james.sieverepository.lib.AbstractSieveRepositoryTest; +import org.joda.time.DateTime; +import org.junit.Test; + +public class CassandraSieveRepositoryTest extends AbstractSieveRepositoryTest { + public static final int DATE_TIMESTAMP = 123456141; + private CassandraCluster cassandra; + + public CassandraSieveRepositoryTest() { + cassandra = CassandraCluster.create(new CassandraSieveRepositoryModule()); + } + + @Override + protected SieveRepository createSieveRepository() throws Exception { + return new CassandraSieveRepository(new CassandraSieveDAO(cassandra.getConf())); + } + + @Override + protected void cleanUp() throws Exception { + cassandra.clearAllTables(); + } + + @Test + public void getActivationDateForActiveScriptShouldWork() throws Exception { + cassandra.getConf().execute( + insertInto(CassandraSieveTable.TABLE_NAME) + .value(CassandraSieveTable.USER_NAME, USER) + .value(CassandraSieveTable.SCRIPT_NAME, SCRIPT_NAME) + .value(CassandraSieveTable.SCRIPT_CONTENT, SCRIPT_CONTENT) + .value(CassandraSieveTable.IS_ACTIVE, true) + .value(CassandraSieveTable.SIZE, SCRIPT_CONTENT.length()) + .value(CassandraSieveTable.DATE, new Date(DATE_TIMESTAMP)) + ); + assertThat(sieveRepository.getActivationDateForActiveScript(USER)).isEqualTo(new DateTime(DATE_TIMESTAMP)); + } + + + @Test(expected = ScriptNotFoundException.class) + public void getActivationDateForActiveScriptShouldThrowOnMissingActiveScript() throws Exception { + cassandra.getConf().execute( + insertInto(CassandraSieveTable.TABLE_NAME) + .value(CassandraSieveTable.USER_NAME, USER) + .value(CassandraSieveTable.SCRIPT_NAME, SCRIPT_NAME) + .value(CassandraSieveTable.SCRIPT_CONTENT, SCRIPT_CONTENT) + .value(CassandraSieveTable.IS_ACTIVE, false) + .value(CassandraSieveTable.SIZE, SCRIPT_CONTENT.length()) + .value(CassandraSieveTable.DATE, DATE_TIMESTAMP) + ); + sieveRepository.getActivationDateForActiveScript(USER); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/model/SieveQuotaTest.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/model/SieveQuotaTest.java b/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/model/SieveQuotaTest.java new file mode 100644 index 0000000..fe93a82 --- /dev/null +++ b/server/data/data-cassandra/src/test/java/org/apache/james/sieve/cassandra/model/SieveQuotaTest.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.cassandra.model; + +import java.util.Optional; + +import org.apache.james.sieverepository.api.exception.QuotaExceededException; +import org.junit.Test; + +public class SieveQuotaTest { + + public static final long INVALID_VALUE = -1L; + public static final long LIMIT_LOW_VALUE = 10L; + public static final long SIZE_DIFFERENCE = 20L; + public static final int CURRENT_USAGE = 0; + public static final long LIMIT_HIGH_VALUE = 100L; + + @Test(expected = IllegalArgumentException.class) + public void sieveQuotaShouldThrowOnNegativeCurrentValue() { + new SieveQuota(INVALID_VALUE, Optional.empty()); + } + + @Test(expected = IllegalArgumentException.class) + public void sieveQuotaShouldThrowOnNegativeLimitValue() { + new SieveQuota(0, Optional.of(INVALID_VALUE)); + } + + @Test(expected = QuotaExceededException.class) + public void checkOverQuotaUponModificationShouldThrowIfLimitExceeded() throws Exception { + new SieveQuota(CURRENT_USAGE, Optional.of(LIMIT_LOW_VALUE)).checkOverQuotaUponModification(SIZE_DIFFERENCE); + } + + @Test + public void checkOverQuotaShouldNotThrowWhenNoLimit() throws Exception { + new SieveQuota(CURRENT_USAGE, Optional.empty()).checkOverQuotaUponModification(SIZE_DIFFERENCE); + } + + @Test + public void checkOverQuotaUponModificationShouldNotThrowIfLimitNotExceeded() throws Exception { + new SieveQuota(CURRENT_USAGE, Optional.of(LIMIT_HIGH_VALUE)).checkOverQuotaUponModification(SIZE_DIFFERENCE); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/96dfc5d5/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 1488791..b07d3dc 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 @@ -31,22 +31,24 @@ 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.joda.time.DateTime; import org.junit.After; import org.junit.Before; import org.junit.Test; public abstract class AbstractSieveRepositoryTest { - private static final String USER = "test"; - private static final String SCRIPT_NAME = "script"; - private static final String SCRIPT_CONTENT = "\u0048\u0065\u006C\u006C\u006F World"; // test utf-8 + protected static final String USER = "test"; + protected static final String SCRIPT_NAME = "script"; + protected static final String SCRIPT_CONTENT = "\u0048\u0065\u006C\u006C\u006F World"; // test utf-8 + private static final String OTHER_SCRIPT_NAME = "other_script"; private static final String OTHER_SCRIPT_CONTENT = "Other script content"; private static final long DEFAULT_QUOTA = Long.MAX_VALUE - 1L; private static final long USER_QUOTA = Long.MAX_VALUE / 2; private static final String UTF8_ENCODING = "UTF-8"; - private SieveRepository sieveRepository; + protected SieveRepository sieveRepository; @Before public void setUp() throws Exception { @@ -70,6 +72,20 @@ public abstract class AbstractSieveRepositoryTest { } @Test + public void getActivationDateForActiveScriptShouldReturnNonNullAndNonZeroResult() throws Exception { + sieveRepository.putScript(USER, SCRIPT_NAME, SCRIPT_CONTENT); + sieveRepository.setActive(USER, SCRIPT_NAME); + assertThat(sieveRepository.getActivationDateForActiveScript(USER)).isNotNull(); + assertThat(sieveRepository.getActivationDateForActiveScript(USER)).isNotEqualTo(new DateTime(0L)); + } + + @Test(expected = ScriptNotFoundException.class) + public void getActivationDateForActiveScriptShouldThrowOnMissingActiveScript() throws Exception { + sieveRepository.putScript(USER, SCRIPT_NAME, SCRIPT_CONTENT); + sieveRepository.getActivationDateForActiveScript(USER); + } + + @Test public void haveSpaceShouldNotThrowWhenUserDoesNotHaveQuota() throws Exception { sieveRepository.haveSpace(USER, SCRIPT_NAME, DEFAULT_QUOTA + 1L); } @@ -185,7 +201,7 @@ public abstract class AbstractSieveRepositoryTest { public void switchOffActiveScriptShouldWork() throws Exception { sieveRepository.putScript(USER, SCRIPT_NAME, SCRIPT_CONTENT); sieveRepository.setActive(USER, SCRIPT_NAME); - sieveRepository.setActive(USER, ""); + sieveRepository.setActive(USER, SieveRepository.NO_SCRIPT_NAME); sieveRepository.getActive(USER); } @@ -193,7 +209,7 @@ public abstract class AbstractSieveRepositoryTest { public void switchOffActiveScriptShouldNotThrow() throws Exception { sieveRepository.putScript(USER, SCRIPT_NAME, SCRIPT_CONTENT); sieveRepository.setActive(USER, SCRIPT_NAME); - sieveRepository.setActive(USER, ""); + sieveRepository.setActive(USER, SieveRepository.NO_SCRIPT_NAME); } @Test(expected = ScriptNotFoundException.class) --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org