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