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 <btell...@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-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org


Reply via email to