You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/04/22 15:09:53 UTC
[james-project] 03/05: JAMES-3777 Add a Cassandra projection for JMAP filters
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit cafc0d9e149be77cf71906b1375ddd0c561535d3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 14 15:20:00 2023 +0700
JAMES-3777 Add a Cassandra projection for JMAP filters
Writes is still O(n2) but using the read projection allows
reads to be O(n). It also enables avoiding LWTs upon reads.
---
.../docs/modules/ROOT/pages/configure/jmap.adoc | 5 +
.../james/modules/data/CassandraJmapModule.java | 34 +++++-
.../filtering/CassandraFilteringProjection.java | 119 +++++++++++++++++++++
.../CassandraFilteringProjectionModule.java} | 30 ++++--
...urcingFilteringManagementNoProjectionTest.java} | 34 ++++--
...sandraEventSourcingFilteringManagementTest.java | 32 +++++-
.../api/filtering/FilteringManagementContract.java | 92 ++++++++--------
...MemoryEventSourcingFilteringManagementTest.java | 15 ++-
src/site/xdoc/server/config-jmap.xml | 6 ++
9 files changed, 294 insertions(+), 73 deletions(-)
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc
index 27ce9a0a01..eb3e31bb14 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc
@@ -87,6 +87,11 @@ then `capabilities."urn:ietf:params:jmap:websocket".url` in response will be "ws
| webpush.prevent.server.side.request.forgery
| Optional boolean. Prevent server side request forgery by preventing calls to the private network ranges. Defaults to true, can be disabled for testing.
+| cassandra.filter.projection.activated
+|Optional boolean. Defaults to false. Casandra backends only. Whether to use or not the Cassandra projection
+for JMAP filters. This projection optimizes reads, but needs to be correctly populated. Turning it on on
+systems with filters already defined would result in those filters to be not read.
+
|===
== Wire tapping
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
index 1dd7a16301..96b1fa5aaa 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
@@ -19,9 +19,13 @@
package org.apache.james.modules.data;
+import java.io.FileNotFoundException;
+
+import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.core.healthcheck.HealthCheck;
import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.eventstore.EventStore;
import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
import org.apache.james.jmap.api.access.AccessTokenRepository;
@@ -38,6 +42,8 @@ import org.apache.james.jmap.cassandra.access.CassandraAccessModule;
import org.apache.james.jmap.cassandra.access.CassandraAccessTokenRepository;
import org.apache.james.jmap.cassandra.change.CassandraEmailChangeModule;
import org.apache.james.jmap.cassandra.change.CassandraMailboxChangeModule;
+import org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjection;
+import org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjectionModule;
import org.apache.james.jmap.cassandra.filtering.FilteringRuleSetDefineDTOModules;
import org.apache.james.jmap.cassandra.identity.CassandraCustomIdentityDAO;
import org.apache.james.jmap.cassandra.identity.CassandraCustomIdentityModule;
@@ -52,14 +58,16 @@ import org.apache.james.jmap.cassandra.upload.UploadConfiguration;
import org.apache.james.jmap.cassandra.upload.UploadDAO;
import org.apache.james.jmap.cassandra.upload.UploadModule;
import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.apache.james.utils.PropertiesProvider;
import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
import com.google.inject.Scopes;
+import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
public class CassandraJmapModule extends AbstractModule {
-
@Override
protected void configure() {
bind(CassandraAccessTokenRepository.class).in(Scopes.SINGLETON);
@@ -73,8 +81,7 @@ public class CassandraJmapModule extends AbstractModule {
bind(CassandraCustomIdentityDAO.class).in(Scopes.SINGLETON);
bind(CustomIdentityDAO.class).to(CassandraCustomIdentityDAO.class);
- bind(EventSourcingFilteringManagement.class).in(Scopes.SINGLETON);
- bind(FilteringManagement.class).to(EventSourcingFilteringManagement.class);
+ bind(CassandraFilteringProjection.class).in(Scopes.SINGLETON);
bind(CassandraPushSubscriptionRepository.class).in(Scopes.SINGLETON);
bind(PushSubscriptionRepository.class).to(CassandraPushSubscriptionRepository.class);
@@ -97,6 +104,7 @@ public class CassandraJmapModule extends AbstractModule {
cassandraDataDefinitions.addBinding().toInstance(CassandraEmailChangeModule.MODULE);
cassandraDataDefinitions.addBinding().toInstance(UploadModule.MODULE);
cassandraDataDefinitions.addBinding().toInstance(CassandraPushSubscriptionModule.MODULE);
+ cassandraDataDefinitions.addBinding().toInstance(CassandraFilteringProjectionModule.MODULE);
cassandraDataDefinitions.addBinding().toInstance(CassandraCustomIdentityModule.MODULE());
Multibinder<EventDTOModule<? extends Event, ? extends EventDTO>> eventDTOModuleBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {});
@@ -108,4 +116,24 @@ public class CassandraJmapModule extends AbstractModule {
.addBinding()
.to(FilterUsernameChangeTaskStep.class);
}
+
+ @Singleton
+ @Provides
+ FilteringManagement provideFilteringManagement(EventStore eventStore, CassandraFilteringProjection cassandraFilteringProjection,
+ PropertiesProvider propertiesProvider) throws ConfigurationException {
+ if (cassandraFilterProjectionActivated(propertiesProvider)) {
+ return new EventSourcingFilteringManagement(eventStore, cassandraFilteringProjection);
+ } else {
+ return new EventSourcingFilteringManagement(eventStore, new EventSourcingFilteringManagement.NoReadProjection(eventStore));
+ }
+ }
+
+ private boolean cassandraFilterProjectionActivated(PropertiesProvider propertiesProvider) throws ConfigurationException {
+ try {
+ return propertiesProvider.getConfiguration("jmap")
+ .getBoolean("cassandra.filter.projection.activated", false);
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
}
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjection.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjection.java
new file mode 100644
index 0000000000..0f0b74d98c
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjection.java
@@ -0,0 +1,119 @@
+package org.apache.james.jmap.cassandra.filtering;
+
+import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker;
+import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto;
+import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom;
+import static org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjectionModule.AGGREGATE_ID;
+import static org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjectionModule.EVENT_ID;
+import static org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjectionModule.RULES;
+import static org.apache.james.jmap.cassandra.filtering.CassandraFilteringProjectionModule.TABLE_NAME;
+
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
+import org.apache.james.core.Username;
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.ReactiveSubscriber;
+import org.apache.james.eventsourcing.Subscriber;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.api.filtering.Version;
+import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement;
+import org.apache.james.jmap.api.filtering.impl.FilteringAggregateId;
+import org.apache.james.jmap.api.filtering.impl.RuleSetDefined;
+import org.reactivestreams.Publisher;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.cql.Row;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import reactor.core.publisher.Mono;
+
+public class CassandraFilteringProjection implements EventSourcingFilteringManagement.ReadProjection, ReactiveSubscriber {
+ private final CassandraAsyncExecutor executor;
+
+ private final PreparedStatement insertStatement;
+ private final PreparedStatement readStatement;
+ private final PreparedStatement readVersionStatement;
+ private final ObjectMapper objectMapper;
+
+ @Inject
+ public CassandraFilteringProjection(CqlSession session) {
+ executor = new CassandraAsyncExecutor(session);
+
+ insertStatement = session.prepare(insertInto(TABLE_NAME)
+ .value(AGGREGATE_ID, bindMarker(AGGREGATE_ID))
+ .value(EVENT_ID, bindMarker(EVENT_ID))
+ .value(RULES, bindMarker(RULES))
+ .build());
+ readStatement = session.prepare(selectFrom(TABLE_NAME).all()
+ .whereColumn(AGGREGATE_ID).isEqualTo(bindMarker(AGGREGATE_ID))
+ .build());
+ readVersionStatement = session.prepare(selectFrom(TABLE_NAME).column(EVENT_ID)
+ .whereColumn(AGGREGATE_ID).isEqualTo(bindMarker(AGGREGATE_ID))
+ .build());
+
+ objectMapper = new ObjectMapper();
+ }
+
+ @Override
+ public Publisher<Rules> listRulesForUser(Username username) {
+ return executor.executeSingleRow(readStatement.bind()
+ .setString(AGGREGATE_ID, new FilteringAggregateId(username).asAggregateKey()))
+ .handle((row, sink) -> {
+ try {
+ Rules rules = parseRules(row);
+ sink.next(rules);
+ } catch (JsonProcessingException e) {
+ sink.error(e);
+ }
+ });
+ }
+
+ @Override
+ public Publisher<Version> getLatestVersion(Username username) {
+ return executor.executeSingleRow(readVersionStatement.bind()
+ .setString(AGGREGATE_ID, new FilteringAggregateId(username).asAggregateKey()))
+ .map(this::parseVersion);
+ }
+
+ @Override
+ public Publisher<Void> handleReactive(Event event) {
+ if (event instanceof RuleSetDefined) {
+ return persist((RuleSetDefined) event);
+ }
+ throw new RuntimeException("Unsupported event");
+ }
+
+ @Override
+ public Optional<Subscriber> subscriber() {
+ return Optional.of(this);
+ }
+
+ private Mono<Void> persist(RuleSetDefined ruleSetDefined) {
+ try {
+ return executor.executeVoid(insertStatement.bind()
+ .setString(AGGREGATE_ID, ruleSetDefined.getAggregateId().asAggregateKey())
+ .setInt(EVENT_ID, ruleSetDefined.eventId().value())
+ .setString(RULES, objectMapper.writeValueAsString(RuleDTO.from(ruleSetDefined.getRules()))));
+ } catch (JsonProcessingException e) {
+ return Mono.error(e);
+ }
+ }
+
+ private Version parseVersion(Row row) {
+ return new Version(row.getInt(EVENT_ID));
+ }
+
+ private Rules parseRules(Row row) throws JsonProcessingException {
+ String serializedRules = row.getString(RULES);
+ List<RuleDTO> ruleDTOS = objectMapper.readValue(serializedRules, new TypeReference<>() {});
+ Version version = parseVersion(row);
+ return new Rules(RuleDTO.toRules(ruleDTOS), version);
+ }
+}
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjectionModule.java
similarity index 59%
copy from server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
copy to server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjectionModule.java
index 9972054f41..5387e795a4 100644
--- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/CassandraFilteringProjectionModule.java
@@ -19,16 +19,24 @@
package org.apache.james.jmap.cassandra.filtering;
-import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreExtension;
-import org.apache.james.eventsourcing.eventstore.cassandra.JsonEventSerializer;
-import org.apache.james.jmap.api.filtering.FilteringManagementContract;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import static com.datastax.oss.driver.api.core.type.DataTypes.INT;
+import static com.datastax.oss.driver.api.core.type.DataTypes.TEXT;
+import static com.datastax.oss.driver.api.core.type.DataTypes.frozenListOf;
-class CassandraEventSourcingFilteringManagementTest implements FilteringManagementContract {
- @RegisterExtension
- static CassandraEventStoreExtension eventStoreExtension =
- new CassandraEventStoreExtension(JsonEventSerializer.forModules(
- FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED,
- FilteringRuleSetDefineDTOModules.FILTERING_INCREMENT)
- .withoutNestedType());
+import org.apache.james.backends.cassandra.components.CassandraModule;
+
+public interface CassandraFilteringProjectionModule {
+ String TABLE_NAME = "filters_projection";
+
+ String AGGREGATE_ID = "aggregate_id";
+ String EVENT_ID = "event_id";
+ String RULES = "rules";
+
+ CassandraModule MODULE = CassandraModule.table(TABLE_NAME)
+ .comment("Holds read projection for the event sourcing system managing JMAP filters.")
+ .statement(statement -> types -> statement
+ .withPartitionKey(AGGREGATE_ID, TEXT)
+ .withColumn(EVENT_ID, INT)
+ .withColumn(RULES, TEXT))
+ .build();
}
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementNoProjectionTest.java
similarity index 54%
copy from server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
copy to server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementNoProjectionTest.java
index 9972054f41..abe1636d3b 100644
--- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
+++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementNoProjectionTest.java
@@ -19,16 +19,38 @@
package org.apache.james.jmap.cassandra.filtering;
-import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreExtension;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStore;
+import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreModule$;
+import org.apache.james.eventsourcing.eventstore.cassandra.EventStoreDao;
import org.apache.james.eventsourcing.eventstore.cassandra.JsonEventSerializer;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
import org.apache.james.jmap.api.filtering.FilteringManagementContract;
+import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
-class CassandraEventSourcingFilteringManagementTest implements FilteringManagementContract {
+class CassandraEventSourcingFilteringManagementNoProjectionTest implements FilteringManagementContract {
@RegisterExtension
- static CassandraEventStoreExtension eventStoreExtension =
- new CassandraEventStoreExtension(JsonEventSerializer.forModules(
+ static CassandraClusterExtension eventStoreExtension = new CassandraClusterExtension(CassandraModule.aggregateModules(
+ CassandraEventStoreModule$.MODULE$.MODULE(),
+ CassandraFilteringProjectionModule.MODULE));
+
+ private EventStore eventStore;
+
+ @BeforeEach
+ void setUp() {
+ eventStore = new CassandraEventStore(new EventStoreDao(eventStoreExtension.getCassandraCluster().getConf(),
+ JsonEventSerializer.forModules(
FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED,
- FilteringRuleSetDefineDTOModules.FILTERING_INCREMENT)
- .withoutNestedType());
+ FilteringRuleSetDefineDTOModules.FILTERING_INCREMENT).withoutNestedType()));
+ }
+
+ @Override
+ public FilteringManagement instantiateFilteringManagement() {
+ return new EventSourcingFilteringManagement(eventStore,
+ new EventSourcingFilteringManagement.NoReadProjection(eventStore));
+ }
}
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
index 9972054f41..714851f218 100644
--- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
+++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/CassandraEventSourcingFilteringManagementTest.java
@@ -19,16 +19,38 @@
package org.apache.james.jmap.cassandra.filtering;
-import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreExtension;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStore;
+import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreModule$;
+import org.apache.james.eventsourcing.eventstore.cassandra.EventStoreDao;
import org.apache.james.eventsourcing.eventstore.cassandra.JsonEventSerializer;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
import org.apache.james.jmap.api.filtering.FilteringManagementContract;
+import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
class CassandraEventSourcingFilteringManagementTest implements FilteringManagementContract {
@RegisterExtension
- static CassandraEventStoreExtension eventStoreExtension =
- new CassandraEventStoreExtension(JsonEventSerializer.forModules(
+ static CassandraClusterExtension eventStoreExtension = new CassandraClusterExtension(CassandraModule.aggregateModules(
+ CassandraEventStoreModule$.MODULE$.MODULE(),
+ CassandraFilteringProjectionModule.MODULE));
+
+ private EventStore eventStore;
+
+ @BeforeEach
+ void setUp() {
+ eventStore = new CassandraEventStore(new EventStoreDao(eventStoreExtension.getCassandraCluster().getConf(),
+ JsonEventSerializer.forModules(
FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED,
- FilteringRuleSetDefineDTOModules.FILTERING_INCREMENT)
- .withoutNestedType());
+ FilteringRuleSetDefineDTOModules.FILTERING_INCREMENT).withoutNestedType()));
+ }
+
+ @Override
+ public FilteringManagement instantiateFilteringManagement() {
+ return new EventSourcingFilteringManagement(eventStore,
+ new CassandraFilteringProjection(eventStoreExtension.getCassandraCluster().getConf()));
+ }
}
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/FilteringManagementContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/FilteringManagementContract.java
index 47ad1ba064..b8cdb93f48 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/FilteringManagementContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/FilteringManagementContract.java
@@ -46,26 +46,24 @@ public interface FilteringManagementContract {
String BART_SIMPSON_CARTOON = "bart@simpson.cartoon";
Username USERNAME = Username.of(BART_SIMPSON_CARTOON);
- default FilteringManagement instantiateFilteringManagement(EventStore eventStore) {
- return new EventSourcingFilteringManagement(eventStore);
- }
+ FilteringManagement instantiateFilteringManagement();
@Test
- default void listingRulesForUnknownUserShouldReturnEmptyList(EventStore eventStore) {
- assertThat(Mono.from(instantiateFilteringManagement(eventStore).listRulesForUser(USERNAME)).block())
+ default void listingRulesForUnknownUserShouldReturnEmptyList() {
+ assertThat(Mono.from(instantiateFilteringManagement().listRulesForUser(USERNAME)).block())
.isEqualTo(new Rules(ImmutableList.of(), new Version(-1)));
}
@Test
- default void listingRulesShouldThrowWhenNullUser(EventStore eventStore) {
+ default void listingRulesShouldThrowWhenNullUser() {
Username username = null;
- assertThatThrownBy(() -> instantiateFilteringManagement(eventStore).listRulesForUser(username))
+ assertThatThrownBy(() -> instantiateFilteringManagement().listRulesForUser(username))
.isInstanceOf(NullPointerException.class);
}
@Test
- default void listingRulesShouldReturnDefinedRules(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void listingRulesShouldReturnDefinedRules() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_1, RULE_2)).block();
@@ -74,8 +72,8 @@ public interface FilteringManagementContract {
}
@Test
- default void listingRulesShouldReturnLastDefinedRules(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void listingRulesShouldReturnLastDefinedRules() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_1, RULE_2)).block();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_2, RULE_1)).block();
@@ -85,24 +83,24 @@ public interface FilteringManagementContract {
}
@Test
- default void definingRulesShouldThrowWhenDuplicateRules(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void definingRulesShouldThrowWhenDuplicateRules() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThatThrownBy(() -> Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_1, RULE_1)).block())
.isInstanceOf(IllegalArgumentException.class);
}
@Test
- default void definingRulesShouldThrowWhenNullUser(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void definingRulesShouldThrowWhenNullUser() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThatThrownBy(() -> Mono.from(testee.defineRulesForUser(null, Optional.empty(), RULE_1, RULE_1)).block())
.isInstanceOf(NullPointerException.class);
}
@Test
- default void definingRulesShouldThrowWhenNullRuleList(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void definingRulesShouldThrowWhenNullRuleList() {
+ FilteringManagement testee = instantiateFilteringManagement();
List<Rule> rules = null;
assertThatThrownBy(() -> Mono.from(testee.defineRulesForUser(USERNAME, rules, Optional.empty())).block())
@@ -110,8 +108,8 @@ public interface FilteringManagementContract {
}
@Test
- default void definingRulesShouldKeepOrdering(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void definingRulesShouldKeepOrdering() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
@@ -120,8 +118,8 @@ public interface FilteringManagementContract {
}
@Test
- default void definingEmptyRuleListShouldRemoveExistingRules(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void definingEmptyRuleListShouldRemoveExistingRules() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
Mono.from(testee.clearRulesForUser(USERNAME)).block();
@@ -131,8 +129,8 @@ public interface FilteringManagementContract {
}
@Test
- default void allFieldsAndComparatorShouldWellBeStored(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void allFieldsAndComparatorShouldWellBeStored() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_FROM, RULE_RECIPIENT, RULE_SUBJECT, RULE_TO, RULE_1)).block();
@@ -141,16 +139,16 @@ public interface FilteringManagementContract {
}
@Test
- default void setRulesWithEmptyVersionShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithEmptyVersionShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThat(Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block())
.isEqualTo(new Version(0));
}
@Test
- default void modifyExistingRulesWithWrongCurrentVersionShouldFail(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void modifyExistingRulesWithWrongCurrentVersionShouldFail() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
@@ -159,8 +157,8 @@ public interface FilteringManagementContract {
}
@Test
- default void modifyExistingRulesWithRightVersionShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void modifyExistingRulesWithRightVersionShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
@@ -169,8 +167,8 @@ public interface FilteringManagementContract {
}
@Test
- default void givenARulesWithVersionIsOneThenUpdateRulesWithIfInStateIsOneShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void givenARulesWithVersionIsOneThenUpdateRulesWithIfInStateIsOneShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2)).block();
@@ -183,16 +181,16 @@ public interface FilteringManagementContract {
}
@Test
- default void setRulesWithEmptyIfInStateWhenNonStateIsDefinedShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithEmptyIfInStateWhenNonStateIsDefinedShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThat(Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2)).block())
.isEqualTo(new Version(0));
}
@Test
- default void setRulesWithEmptyIfInStateWhenAStateIsDefinedShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithEmptyIfInStateWhenAStateIsDefinedShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2)).block();
@@ -201,16 +199,16 @@ public interface FilteringManagementContract {
}
@Test
- default void setRulesWithIfInStateIsInitialWhenNonStateIsDefinedShouldSucceed(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithIfInStateIsInitialWhenNonStateIsDefinedShouldSucceed() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThat(Mono.from(testee.defineRulesForUser(USERNAME, Optional.of(Version.INITIAL), RULE_3, RULE_2)).block())
.isEqualTo(new Version(0));
}
@Test
- default void setRulesWithIfInStateIsInitialWhenAStateIsDefinedShouldFail(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithIfInStateIsInitialWhenAStateIsDefinedShouldFail() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
@@ -222,24 +220,24 @@ public interface FilteringManagementContract {
}
@Test
- default void setRulesWithIfInStateIsOneWhenNonStateIsDefinedShouldFail(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void setRulesWithIfInStateIsOneWhenNonStateIsDefinedShouldFail() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThatThrownBy(() -> Mono.from(testee.defineRulesForUser(USERNAME, Optional.of(new Version(1)), RULE_2, RULE_1)).block())
.isInstanceOf(StateMismatchException.class);
}
@Test
- default void getLatestVersionWhenNonVersionIsDefinedShouldReturnVersionInitial(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void getLatestVersionWhenNonVersionIsDefinedShouldReturnVersionInitial() {
+ FilteringManagement testee = instantiateFilteringManagement();
assertThat(Mono.from(testee.getLatestVersion(USERNAME)).block())
.isEqualTo(Version.INITIAL);
}
@Test
- default void getLatestVersionAfterSetRulesFirstTimeShouldReturnVersionZero(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void getLatestVersionAfterSetRulesFirstTimeShouldReturnVersionZero() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
@@ -248,8 +246,8 @@ public interface FilteringManagementContract {
}
@Test
- default void getLatestVersionAfterSetRulesNotSucceedShouldReturnOldVersion(EventStore eventStore) {
- FilteringManagement testee = instantiateFilteringManagement(eventStore);
+ default void getLatestVersionAfterSetRulesNotSucceedShouldReturnOldVersion() {
+ FilteringManagement testee = instantiateFilteringManagement();
Mono.from(testee.defineRulesForUser(USERNAME, Optional.empty(), RULE_3, RULE_2, RULE_1)).block();
assertThat(Mono.from(testee.getLatestVersion(USERNAME)).block())
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/InMemoryEventSourcingFilteringManagementTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/InMemoryEventSourcingFilteringManagementTest.java
index 39dffe7002..1dc1f33af1 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/InMemoryEventSourcingFilteringManagementTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/InMemoryEventSourcingFilteringManagementTest.java
@@ -19,11 +19,24 @@
package org.apache.james.jmap.api.filtering.impl;
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStore;
import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStoreExtension;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
import org.apache.james.jmap.api.filtering.FilteringManagementContract;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
-@ExtendWith(InMemoryEventStoreExtension.class)
public class InMemoryEventSourcingFilteringManagementTest implements FilteringManagementContract {
+ private EventStore eventStore;
+ @BeforeEach
+ void setUp() {
+ eventStore = new InMemoryEventStore();
+ }
+
+ @Override
+ public FilteringManagement instantiateFilteringManagement() {
+ return new EventSourcingFilteringManagement(eventStore);
+ }
}
diff --git a/src/site/xdoc/server/config-jmap.xml b/src/site/xdoc/server/config-jmap.xml
index 9cfc5fa1c7..ff375f99ad 100644
--- a/src/site/xdoc/server/config-jmap.xml
+++ b/src/site/xdoc/server/config-jmap.xml
@@ -125,6 +125,12 @@
<dd>Optional boolean. Prevent server side request forgery by preventing calls to the private network
ranges. Defaults to true, can be disabled for testing.
</dd>
+
+ <dt><strong>cassandra.filter.projection.activated</strong></dt>
+ <dd>Optional boolean. Defaults to false. Casandra backends only. Whether to use or not the Cassandra projection
+ for JMAP filters. This projection optimizes reads, but needs to be correctly populated. Turning it on on
+ systems with filters already defined would result in those filters to be not read.
+ </dd>
</dl>
</subsection>
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org