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
The following commit(s) were added to refs/heads/master by this push: new 76ac78344d JAMES-3908 implement JMAP filtering: combine rule conditions (#1643) 76ac78344d is described below commit 76ac78344d489d2935b62d8cfefc5266eba3588d Author: hungphan227 <45198168+hungphan...@users.noreply.github.com> AuthorDate: Thu Jul 20 09:53:14 2023 +0700 JAMES-3908 implement JMAP filtering: combine rule conditions (#1643) --- .../james/jmap/cassandra/filtering/RuleDTO.java | 86 ++++++++++-- .../james/jmap/cassandra/filtering/DTOTest.java | 32 ++++- .../src/test/resources/json/event-v3.json | 53 ++++++++ .../src/test/resources/json/eventComplex-v3.json | 99 ++++++++++++++ .../src/test/resources/json/increment-v3.json | 114 ++++++++++++++++ .../org/apache/james/jmap/api/filtering/Rule.java | 90 +++++++++++-- .../james/jmap/api/filtering/RuleFixture.java | 68 +++++----- .../apache/james/jmap/api/filtering/RuleTest.java | 24 ++-- .../impl/FilterUsernameChangeTaskStepTest.java | 2 +- .../apache/james/jmap/draft/model/JmapRuleDTO.java | 8 +- .../james/jmap/mailet/filter/MailMatcher.java | 75 ++++++++--- .../jmap/mailet/filter/JMAPFilteringExtension.java | 2 +- .../jmap/mailet/filter/JMAPFilteringTest.java | 150 +++++++++++++++------ .../memory/MemoryUserDeletionIntegrationTest.java | 2 +- .../MemoryUsernameChangeIntegrationTest.java | 2 +- ...pulateFilteringProjectionRequestToTaskTest.java | 2 +- 16 files changed, 668 insertions(+), 141 deletions(-) diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/RuleDTO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/RuleDTO.java index 86a39ac00a..a653d90e11 100644 --- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/RuleDTO.java +++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/RuleDTO.java @@ -21,6 +21,7 @@ package org.apache.james.jmap.cassandra.filtering; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.james.jmap.api.filtering.Rule; @@ -32,6 +33,50 @@ import com.google.common.collect.ImmutableList; public class RuleDTO { + public static class ConditionGroupDTO { + + private final Rule.ConditionCombiner conditionCombiner; + private final List<ConditionDTO> conditionDTOs; + + @JsonCreator + public ConditionGroupDTO(@JsonProperty("conditionCombiner") Rule.ConditionCombiner conditionCombiner, + @JsonProperty("conditions") List<ConditionDTO> conditionDTOs) { + this.conditionCombiner = conditionCombiner; + this.conditionDTOs = conditionDTOs; + } + + public Rule.ConditionCombiner getConditionCombiner() { + return conditionCombiner; + } + + public List<ConditionDTO> getConditions() { + return conditionDTOs; + } + + public Rule.ConditionGroup toConditionGroup() { + return Rule.ConditionGroup.of(conditionCombiner, conditionDTOs.stream().map(ConditionDTO::toCondition).collect(ImmutableList.toImmutableList())); + } + + public static ConditionGroupDTO from(Rule.ConditionGroup conditionGroup) { + return new ConditionGroupDTO(conditionGroup.getConditionCombiner(), conditionGroup.getConditions().stream().map(ConditionDTO::from).collect(ImmutableList.toImmutableList())); + } + + @Override + public final boolean equals(Object o) { + if (o instanceof ConditionGroupDTO) { + ConditionGroupDTO other = (ConditionGroupDTO) o; + return Objects.equals(conditionCombiner, other.conditionCombiner) + && Objects.equals(conditionDTOs, other.conditionDTOs); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(conditionCombiner, conditionDTOs); + } + } + public static class ConditionDTO { public static ConditionDTO from(Rule.Condition condition) { @@ -215,24 +260,44 @@ public class RuleDTO { public static RuleDTO from(Rule rule) { return new RuleDTO(rule.getId().asString(), rule.getName(), - ConditionDTO.from(rule.getCondition()), + ConditionGroupDTO.from(rule.getConditionGroup()), ActionDTO.from(rule.getAction())); } private final String id; private final String name; - private final ConditionDTO conditionDTO; + private final ConditionGroupDTO conditionGroupDTO; private final ActionDTO actionDTO; + public RuleDTO(String id, + String name, + ConditionGroupDTO conditionGroupDTO, + ActionDTO actionDTO) { + Preconditions.checkNotNull(id); + + this.name = name; + this.conditionGroupDTO = conditionGroupDTO; + this.actionDTO = actionDTO; + + this.id = id; + } + @JsonCreator public RuleDTO(@JsonProperty("id") String id, @JsonProperty("name") String name, - @JsonProperty("condition") ConditionDTO conditionDTO, + @JsonProperty("condition") Optional<ConditionDTO> conditionDTO, + @JsonProperty("conditionGroup") Optional<ConditionGroupDTO> conditionGroupDTO, @JsonProperty("action") ActionDTO actionDTO) { + Preconditions.checkNotNull(id); + this.name = name; - this.conditionDTO = conditionDTO; + if (conditionGroupDTO.isPresent()) { + this.conditionGroupDTO = conditionGroupDTO.orElseThrow(); + } else { + this.conditionGroupDTO = new ConditionGroupDTO(Rule.ConditionCombiner.AND, + ImmutableList.of(conditionDTO.orElseThrow(() -> new RuntimeException("Condition field in the rule with id " + id + " is missing")))); + } this.actionDTO = actionDTO; - Preconditions.checkNotNull(id); this.id = id; } @@ -245,8 +310,8 @@ public class RuleDTO { return name; } - public ConditionDTO getCondition() { - return conditionDTO; + public ConditionGroupDTO getConditionGroup() { + return conditionGroupDTO; } public ActionDTO getAction() { @@ -257,8 +322,7 @@ public class RuleDTO { return Rule.builder() .id(Rule.Id.of(id)) .name(name) - .condition(conditionDTO.toCondition()) - .name(name) + .conditionGroup(conditionGroupDTO.toConditionGroup()) .action(actionDTO.toAction()) .build(); } @@ -270,7 +334,7 @@ public class RuleDTO { return Objects.equals(this.id, ruleDTO.id) && Objects.equals(this.name, ruleDTO.name) - && Objects.equals(this.conditionDTO, ruleDTO.conditionDTO) + && Objects.equals(this.conditionGroupDTO, ruleDTO.conditionGroupDTO) && Objects.equals(this.actionDTO, ruleDTO.actionDTO); } return false; @@ -278,7 +342,7 @@ public class RuleDTO { @Override public final int hashCode() { - return Objects.hash(id, name, conditionDTO, actionDTO); + return Objects.hash(id, name, conditionGroupDTO, actionDTO); } @Override diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java index 25ca063d4a..8a09eb880a 100644 --- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java +++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java @@ -29,8 +29,6 @@ import static org.apache.james.jmap.cassandra.filtering.FilteringRuleSetDefineDT import static org.apache.james.jmap.cassandra.filtering.FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Optional; - import org.apache.james.JsonSerializationVerifier; import org.apache.james.core.Username; import org.apache.james.eventsourcing.EventId; @@ -50,9 +48,11 @@ import com.google.common.collect.ImmutableSet; class DTOTest { static final String EVENT_JSON = ClassLoaderUtils.getSystemResourceAsString("json/event.json"); static final String EVENT_JSON_2 = ClassLoaderUtils.getSystemResourceAsString("json/event-v2.json"); + static final String EVENT_JSON_3 = ClassLoaderUtils.getSystemResourceAsString("json/event-v3.json"); static final String EVENT_EMPTY_JSON = ClassLoaderUtils.getSystemResourceAsString("json/eventEmpty.json"); static final String EVENT_COMPLEX_JSON = ClassLoaderUtils.getSystemResourceAsString("json/eventComplex.json"); static final String EVENT_COMPLEX_JSON_2 = ClassLoaderUtils.getSystemResourceAsString("json/eventComplex-v2.json"); + static final String EVENT_COMPLEX_JSON_3 = ClassLoaderUtils.getSystemResourceAsString("json/eventComplex-v3.json"); static final RuleSetDefined SIMPLE_RULE = new RuleSetDefined( new FilteringAggregateId(Username.of("Bart")), @@ -78,11 +78,23 @@ class DTOTest { void shouldSerializeRule() throws Exception { JsonSerializationVerifier.dtoModule(FILTERING_RULE_SET_DEFINED) .testCase(EMPTY_RULE, EVENT_EMPTY_JSON) - .testCase(SIMPLE_RULE, EVENT_JSON_2) - .testCase(COMPLEX_RULE, EVENT_COMPLEX_JSON_2) + .testCase(SIMPLE_RULE, EVENT_JSON_3) + .testCase(COMPLEX_RULE, EVENT_COMPLEX_JSON_3) .verify(); } + @Test + void shouldDeserializeV2() { + JsonGenericSerializer<RuleSetDefined, FilteringRuleSetDefinedDTO> serializer = JsonGenericSerializer + .forModules(FILTERING_RULE_SET_DEFINED) + .withoutNestedType(); + + SoftAssertions.assertSoftly(Throwing.consumer(softly -> { + softly.assertThat(serializer.deserialize(EVENT_JSON_2)).isEqualToComparingFieldByFieldRecursively(SIMPLE_RULE); + softly.assertThat(serializer.deserialize(EVENT_COMPLEX_JSON_2)).isEqualToComparingFieldByFieldRecursively(COMPLEX_RULE); + })); + } + @Test void shouldDeserializeV1() { JsonGenericSerializer<RuleSetDefined, FilteringRuleSetDefinedDTO> serializer = JsonGenericSerializer @@ -98,10 +110,20 @@ class DTOTest { @Test void shouldSerializeIncrements() throws Exception { JsonSerializationVerifier.dtoModule(FILTERING_INCREMENT) - .testCase(INCREMENT, ClassLoaderUtils.getSystemResourceAsString("json/increment-v2.json")) + .testCase(INCREMENT, ClassLoaderUtils.getSystemResourceAsString("json/increment-v3.json")) .verify(); } + @Test + void shouldDeserializeV2ForIncrements() throws Exception { + JsonGenericSerializer<IncrementalRuleChange, FilteringIncrementalRuleChangeDTO> serializer = JsonGenericSerializer + .forModules(FILTERING_INCREMENT) + .withoutNestedType(); + + assertThat(serializer.deserialize(ClassLoaderUtils.getSystemResourceAsString("json/increment-v2.json"))) + .isEqualToComparingFieldByFieldRecursively(INCREMENT); + } + @Test void shouldDeserializeV1ForIncrements() throws Exception { JsonGenericSerializer<IncrementalRuleChange, FilteringIncrementalRuleChangeDTO> serializer = JsonGenericSerializer diff --git a/server/data/data-jmap-cassandra/src/test/resources/json/event-v3.json b/server/data/data-jmap-cassandra/src/test/resources/json/event-v3.json new file mode 100644 index 0000000000..699f289a80 --- /dev/null +++ b/server/data/data-jmap-cassandra/src/test/resources/json/event-v3.json @@ -0,0 +1,53 @@ +{ + "type":"filtering-rule-set-defined", + "eventId":0, + "aggregateId":"FilteringRule/bart", + "rules":[ + { + "id":"1", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "cc", + "comparator": "contains", + "value": "something" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["id-01"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + }, + { + "id":"2", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "cc", + "comparator": "contains", + "value": "something" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["id-01"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + } + ] +} \ No newline at end of file diff --git a/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v3.json b/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v3.json new file mode 100644 index 0000000000..66cdae15f2 --- /dev/null +++ b/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v3.json @@ -0,0 +1,99 @@ +{ + "type":"filtering-rule-set-defined", + "eventId":0, + "aggregateId":"FilteringRule/bart", + "rules":[ + { + "id":"id-from", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "from", + "comparator": "contains", + "value": "A value to match 4" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["mbx1"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + }, + { + "id":"id-rcpt", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "recipient", + "comparator": "not-exactly-equals", + "value": "A value to match 3" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["mbx1"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + }, + { + "id":"id-subject", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "subject", + "comparator": "not-contains", + "value": "A value to match 2" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["mbx1"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + }, + { + "id":"id-to", + "name":"a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "to", + "comparator": "exactly-equals", + "value": "A value to match 1" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds":["mbx1"] + }, + "important":false, + "keyworkds":[], + "reject":false, + "seen":false + } + } + ] +} \ No newline at end of file diff --git a/server/data/data-jmap-cassandra/src/test/resources/json/increment-v3.json b/server/data/data-jmap-cassandra/src/test/resources/json/increment-v3.json new file mode 100644 index 0000000000..65bfd0590f --- /dev/null +++ b/server/data/data-jmap-cassandra/src/test/resources/json/increment-v3.json @@ -0,0 +1,114 @@ +{ + "type": "filtering-increment", + "eventId": 0, + "aggregateId": "FilteringRule/bart", + "prepended": [ + { + "id": "id-from", + "name": "a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "from", + "comparator": "contains", + "value": "A value to match 4" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds": [ + "mbx1" + ] + }, + "important": false, + "keyworkds": [], + "reject": false, + "seen": false + } + }, + { + "id": "id-to", + "name": "a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "to", + "comparator": "exactly-equals", + "value": "A value to match 1" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds": [ + "mbx1" + ] + }, + "important": false, + "keyworkds": [], + "reject": false, + "seen": false + } + } + ], + "postpended": [ + { + "id": "id-rcpt", + "name": "a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "recipient", + "comparator": "not-exactly-equals", + "value": "A value to match 3" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds": [ + "mbx1" + ] + }, + "important": false, + "keyworkds": [], + "reject": false, + "seen": false + } + } + ], + "updated": [ + { + "id": "id-subject", + "name": "a name", + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "field": "subject", + "comparator": "not-contains", + "value": "A value to match 2" + } + ] + }, + "action": { + "appendIn": { + "mailboxIds": [ + "mbx1" + ] + }, + "important": false, + "keyworkds": [], + "reject": false, + "seen": false + } + } + ], + "deleted": [ + "abdcd" + ] +} \ No newline at end of file diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java index 91c79317fa..bed13dad21 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java @@ -72,6 +72,60 @@ public class Rule { } } + public static class ConditionGroup { + + public static ConditionGroup of(ConditionCombiner conditionCombiner, List<Condition> conditions) { + return new ConditionGroup(conditionCombiner, conditions); + } + + public static ConditionGroup of(ConditionCombiner conditionCombiner, Condition... conditions) { + return new ConditionGroup(conditionCombiner, ImmutableList.copyOf(conditions)); + } + + public static ConditionGroup of(Condition condition) { + return ConditionGroup.of(ConditionCombiner.AND, condition); + } + + private final ConditionCombiner conditionCombiner; + private final List<Condition> conditions; + + private ConditionGroup(ConditionCombiner conditionCombiner, List<Condition> conditions) { + this.conditionCombiner = conditionCombiner; + this.conditions = conditions; + } + + public ConditionCombiner getConditionCombiner() { + return conditionCombiner; + } + + public List<Condition> getConditions() { + return conditions; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof ConditionGroup) { + ConditionGroup other = (ConditionGroup) o; + return Objects.equals(conditionCombiner, other.conditionCombiner) + && Objects.equals(conditions, other.conditions); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(conditionCombiner, conditions); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("conditionCombiner", conditionCombiner) + .add("conditions", conditions) + .toString(); + } + } + public static class Condition { public enum Field { @@ -306,7 +360,7 @@ public class Rule { private Id id; private String name; - private Condition condition; + private ConditionGroup conditionGroup; private Action action; public Builder id(Id id) { @@ -319,8 +373,13 @@ public class Rule { return this; } - public Builder condition(Condition condition) { - this.condition = condition; + public Builder conditionGroup(Condition condition) { + this.conditionGroup = Rule.ConditionGroup.of(condition); + return this; + } + + public Builder conditionGroup(ConditionGroup conditionGroup) { + this.conditionGroup = conditionGroup; return this; } @@ -332,10 +391,10 @@ public class Rule { public Rule build() { Preconditions.checkState(id != null, "`id` is mandatory"); Preconditions.checkState(StringUtils.isNotBlank(name), "`name` is mandatory"); - Preconditions.checkState(condition != null, "`condition` is mandatory"); + Preconditions.checkState(conditionGroup != null, "`conditions` is mandatory"); Preconditions.checkState(action != null, "`action` is mandatory"); - return new Rule(id, name, condition, action); + return new Rule(id, name, conditionGroup, action); } } @@ -344,15 +403,20 @@ public class Rule { return new Builder(); } + public enum ConditionCombiner { + AND, + OR + } + private final Id id; private final String name; - private final Condition condition; + private final ConditionGroup conditionGroup; private final Action action; - private Rule(Id id, String name, Condition condition, Action action) { + private Rule(Id id, String name, ConditionGroup conditionGroup, Action action) { this.id = id; this.name = name; - this.condition = condition; + this.conditionGroup = conditionGroup; this.action = action; } @@ -364,8 +428,8 @@ public class Rule { return name; } - public Condition getCondition() { - return condition; + public ConditionGroup getConditionGroup() { + return conditionGroup; } public Action getAction() { @@ -379,7 +443,7 @@ public class Rule { return Objects.equals(this.id, rule.id) && Objects.equals(this.name, rule.name) - && Objects.equals(this.condition, rule.condition) + && Objects.equals(this.conditionGroup, rule.conditionGroup) && Objects.equals(this.action, rule.action); } return false; @@ -387,7 +451,7 @@ public class Rule { @Override public final int hashCode() { - return Objects.hash(id, name, condition, action); + return Objects.hash(id, name, conditionGroup, action); } @Override @@ -395,7 +459,7 @@ public class Rule { return MoreObjects.toStringHelper(this) .add("id", id) .add("name", name) - .add("condition", condition) + .add("conditionGroup", conditionGroup) .add("action", action) .toString(); } diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java index d39bb25d2c..256070dc54 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java @@ -23,10 +23,10 @@ public interface RuleFixture { String NAME = "a name"; Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something"); Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01")); - Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).condition(CONDITION).action(ACTION); + Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).conditionGroup(CONDITION).action(ACTION); Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build(); Rule RULE_1_MODIFIED = Rule.builder() - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .id(Rule.Id.of("1")) .name("newname") @@ -35,43 +35,43 @@ public interface RuleFixture { Rule RULE_3 = RULE_BUILDER.id(Rule.Id.of("3")).build(); Rule RULE_TO = Rule.builder() - .id(Rule.Id.of("id-to")) - .name(NAME) - .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) - .condition(Rule.Condition.of( - Rule.Condition.Field.TO, - Rule.Condition.Comparator.EXACTLY_EQUALS, - "A value to match 1")) - .build(); + .id(Rule.Id.of("id-to")) + .name(NAME) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) + .conditionGroup(Rule.Condition.of( + Rule.Condition.Field.TO, + Rule.Condition.Comparator.EXACTLY_EQUALS, + "A value to match 1")) + .build(); Rule RULE_SUBJECT = Rule.builder() - .id(Rule.Id.of("id-subject")) - .name(NAME) - .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) - .condition(Rule.Condition.of( - Rule.Condition.Field.SUBJECT, - Rule.Condition.Comparator.NOT_CONTAINS, - "A value to match 2")) - .build(); + .id(Rule.Id.of("id-subject")) + .name(NAME) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) + .conditionGroup(Rule.Condition.of( + Rule.Condition.Field.SUBJECT, + Rule.Condition.Comparator.NOT_CONTAINS, + "A value to match 2")) + .build(); Rule RULE_RECIPIENT = Rule.builder() - .id(Rule.Id.of("id-rcpt")) - .name(NAME) - .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) - .condition(Rule.Condition.of( - Rule.Condition.Field.RECIPIENT, - Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, - "A value to match 3")) - .build(); + .id(Rule.Id.of("id-rcpt")) + .name(NAME) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) + .conditionGroup(Rule.Condition.of( + Rule.Condition.Field.RECIPIENT, + Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, + "A value to match 3")) + .build(); Rule RULE_FROM = Rule.builder() - .id(Rule.Id.of("id-from")) - .name(NAME) - .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) - .condition(Rule.Condition.of( - Rule.Condition.Field.FROM, - Rule.Condition.Comparator.CONTAINS, - "A value to match 4")) - .build(); + .id(Rule.Id.of("id-from")) + .name(NAME) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("mbx1"))) + .conditionGroup(Rule.Condition.of( + Rule.Condition.Field.FROM, + Rule.Condition.Comparator.CONTAINS, + "A value to match 4")) + .build(); } diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleTest.java index 830f975b23..37dc1263bf 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleTest.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleTest.java @@ -51,6 +51,12 @@ class RuleTest { .verify(); } + @Test + void innerClassConditionGroupShouldMatchBeanContract() { + EqualsVerifier.forClass(Rule.ConditionGroup.class) + .verify(); + } + @Test void innerClassActionShouldMatchBeanContract() { EqualsVerifier.forClass(Rule.Action.class) @@ -89,7 +95,7 @@ class RuleTest { assertThatThrownBy(() -> Rule.builder() .name(NAME) - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build()) .isInstanceOf(IllegalStateException.class); @@ -100,7 +106,7 @@ class RuleTest { assertThatThrownBy(() -> Rule.builder() .id(UNIQUE_ID) - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build()) .isInstanceOf(IllegalStateException.class); @@ -112,7 +118,7 @@ class RuleTest { Rule.builder() .id(UNIQUE_ID) .name("") - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build()) .isInstanceOf(IllegalStateException.class); @@ -124,7 +130,7 @@ class RuleTest { Rule.builder() .id(UNIQUE_ID) .name(" ") - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build()) .isInstanceOf(IllegalStateException.class); @@ -136,7 +142,7 @@ class RuleTest { Rule.builder() .id(UNIQUE_ID) .name(null) - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build()) .isInstanceOf(IllegalStateException.class); @@ -159,7 +165,7 @@ class RuleTest { Rule.builder() .id(UNIQUE_ID) .name(NAME) - .condition(CONDITION) + .conditionGroup(CONDITION) .build()) .isInstanceOf(IllegalStateException.class); } @@ -169,11 +175,11 @@ class RuleTest { Rule rule = Rule.builder() .id(UNIQUE_ID) .name(NAME) - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build(); - assertThat(rule.getCondition()).isEqualTo(CONDITION); + assertThat(rule.getConditionGroup().getConditions().get(0)).isEqualTo(CONDITION); } @Test @@ -181,7 +187,7 @@ class RuleTest { Rule rule = Rule.builder() .id(UNIQUE_ID) .name(NAME) - .condition(CONDITION) + .conditionGroup(CONDITION) .action(ACTION) .build(); diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java index 6a4c89a289..3ef04bdba1 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java @@ -44,7 +44,7 @@ public class FilterUsernameChangeTaskStepTest { private static final String NAME = "a name"; private static final Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something"); private static final Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01")); - private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).condition(CONDITION).action(ACTION); + private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).conditionGroup(CONDITION).action(ACTION); private static final Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build(); private static final Rule RULE_2 = RULE_BUILDER.id(Rule.Id.of("2")).build(); diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/JmapRuleDTO.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/JmapRuleDTO.java index a4e2478dea..1823766f02 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/JmapRuleDTO.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/JmapRuleDTO.java @@ -136,9 +136,9 @@ public class JmapRuleDTO { public static JmapRuleDTO from(Rule rule) { return new JmapRuleDTO(rule.getId().asString(), - rule.getName(), - ConditionDTO.from(rule.getCondition()), - ActionDTO.from(rule.getAction())); + rule.getName(), + ConditionDTO.from(rule.getConditionGroup().getConditions().get(0)), + ActionDTO.from(rule.getAction())); } private final String id; @@ -177,7 +177,7 @@ public class JmapRuleDTO { return Rule.builder() .id(Rule.Id.of(id)) .name(name) - .condition(conditionDTO.toCondition()) + .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, ImmutableList.of(conditionDTO.toCondition()))) .action(actionDTO.toAction()) .build(); } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java index ed5db25a33..f583561cad 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java @@ -19,9 +19,7 @@ package org.apache.james.jmap.mailet.filter; -import static org.apache.james.jmap.api.filtering.Rule.Condition; - -import java.util.Optional; +import java.util.List; import java.util.stream.Stream; import org.apache.james.jmap.api.filtering.Rule; @@ -30,6 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; public interface MailMatcher { @@ -37,11 +37,43 @@ public interface MailMatcher { private static final Logger LOGGER = LoggerFactory.getLogger(HeaderMatcher.class); + private List<MailMatchingCondition> mailMatchingConditions; + private final Rule.ConditionCombiner conditionCombiner; + + private HeaderMatcher(List<MailMatchingCondition> mailMatchingConditions, Rule.ConditionCombiner conditionCombiner) { + this.mailMatchingConditions = mailMatchingConditions; + this.conditionCombiner = conditionCombiner; + } + + @Override + public boolean match(Mail mail) { + try { + Predicate<MailMatchingCondition> predicate = (MailMatchingCondition mailMatchingCondition) -> { + Stream<String> headerLines = mailMatchingCondition.getHeaderExtractor().apply(mail); + return mailMatchingCondition.getContentMatcher().match(headerLines, mailMatchingCondition.getRuleValue()); + }; + + switch (conditionCombiner) { + case AND: + return mailMatchingConditions.stream().allMatch(predicate); + case OR: + return mailMatchingConditions.stream().anyMatch(predicate); + default: + throw new Exception(conditionCombiner + " conditionCombiner is not supported"); + } + } catch (Exception e) { + LOGGER.error("error while extracting mail header", e); + return false; + } + } + } + + class MailMatchingCondition { private final ContentMatcher contentMatcher; private final String ruleValue; private final HeaderExtractor headerExtractor; - private HeaderMatcher(ContentMatcher contentMatcher, String ruleValue, + private MailMatchingCondition(ContentMatcher contentMatcher, String ruleValue, HeaderExtractor headerExtractor) { Preconditions.checkNotNull(contentMatcher); Preconditions.checkNotNull(headerExtractor); @@ -51,27 +83,28 @@ public interface MailMatcher { this.headerExtractor = headerExtractor; } - @Override - public boolean match(Mail mail) { - try { - Stream<String> headerLines = headerExtractor.apply(mail); - return contentMatcher.match(headerLines, ruleValue); - } catch (Exception e) { - LOGGER.error("error while extracting mail header", e); - return false; - } + public ContentMatcher getContentMatcher() { + return contentMatcher; + } + + public String getRuleValue() { + return ruleValue; + } + + public HeaderExtractor getHeaderExtractor() { + return headerExtractor; } } static MailMatcher from(Rule rule) { - Condition ruleCondition = rule.getCondition(); - Optional<ContentMatcher> maybeContentMatcher = ContentMatcher.asContentMatcher(ruleCondition.getField(), ruleCondition.getComparator()); - Optional<HeaderExtractor> maybeHeaderExtractor = HeaderExtractor.asHeaderExtractor(ruleCondition.getField()); - - return new HeaderMatcher( - maybeContentMatcher.orElseThrow(() -> new RuntimeException("No content matcher associated with field " + ruleCondition.getField())), - rule.getCondition().getValue(), - maybeHeaderExtractor.orElseThrow(() -> new RuntimeException("No content matcher associated with comparator " + ruleCondition.getComparator()))); + return new HeaderMatcher(rule.getConditionGroup().getConditions().stream() + .map(ruleCondition -> new MailMatchingCondition( + ContentMatcher.asContentMatcher(ruleCondition.getField(), ruleCondition.getComparator()) + .orElseThrow(() -> new RuntimeException("No content matcher associated with field " + ruleCondition.getField())), + ruleCondition.getValue(), + HeaderExtractor.asHeaderExtractor(ruleCondition.getField()) + .orElseThrow(() -> new RuntimeException("No content matcher associated with comparator " + ruleCondition.getComparator()))) + ).collect(ImmutableList.toImmutableList()), rule.getConditionGroup().getConditionCombiner()); } boolean match(Mail mail); diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java index 3696092b1b..64e1f93c00 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java @@ -112,7 +112,7 @@ public class JMAPFilteringExtension implements BeforeEachCallback, ParameterReso .map(condition -> Rule.builder() .id(Rule.Id.of(String.valueOf(counter.incrementAndGet()))) .name(String.valueOf(counter.incrementAndGet())) - .condition(condition) + .conditionGroup(condition) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) .build()) .collect(ImmutableList.toImmutableList()); diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java index a4c1390aa4..df92049b28 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java @@ -46,6 +46,7 @@ import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_FU import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_USERNAME; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_2_ADDRESS; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_2_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_2_USERNAME; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_3_ADDRESS; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_3_FULL_ADDRESS; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_4_FULL_ADDRESS; @@ -98,7 +99,7 @@ class JMAPFilteringTest { private Optional<Rule.Condition.Field> field; private MimeMessageBuilder mimeMessageBuilder; private Optional<String> valueToMatch; - + private FilteringArgumentBuilder() { this.description = Optional.empty(); this.field = Optional.empty(); @@ -178,9 +179,9 @@ class JMAPFilteringTest { public FilteringArgumentBuilder unscrambledSubjectShouldNotMatchCaseSensitive() { return description("unscrambled content (case sensitive)") - .field(SUBJECT) - .subject(UNSCRAMBLED_SUBJECT) - .valueToMatch(UNSCRAMBLED_SUBJECT.toUpperCase(Locale.FRENCH)); + .field(SUBJECT) + .subject(UNSCRAMBLED_SUBJECT) + .valueToMatch(UNSCRAMBLED_SUBJECT.toUpperCase(Locale.FRENCH)); } public FilteringArgumentBuilder testForUpperCase() { @@ -192,7 +193,7 @@ class JMAPFilteringTest { Preconditions.checkState(description.isPresent()); Preconditions.checkState(field.isPresent()); Preconditions.checkState(valueToMatch.isPresent()); - + return Arguments.of(description.get(), field.get(), mimeMessageBuilder, valueToMatch.get()); } } @@ -599,23 +600,23 @@ class JMAPFilteringTest { testSystem.getJmapFiltering().service(mail); assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) - .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); + .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); } @ParameterizedTest(name = "CONTAINS should not match for field {1}: {0}") @MethodSource("notContainsTestSuite") void notMatchingContainsTest(String testDescription, - Rule.Condition.Field fieldToMatch, - MimeMessageBuilder mimeMessageBuilder, - String valueToMatch, - JMAPFilteringTestSystem testSystem) throws Exception { + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, CONTAINS, valueToMatch)); FakeMail mail = testSystem.asMail(mimeMessageBuilder); testSystem.getJmapFiltering().service(mail); assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) - .isEmpty(); + .isEmpty(); } @ParameterizedTest(name = "NOT-CONTAINS should match for field {1}: {0}") @@ -669,10 +670,10 @@ class JMAPFilteringTest { @ParameterizedTest(name = "EXACTLY-EQUALS should not match for field {1}: {0}") @MethodSource("notEqualsTestSuite") void equalsNotMatchingTest(String testDescription, - Rule.Condition.Field fieldToMatch, - MimeMessageBuilder mimeMessageBuilder, - String valueToMatch, - JMAPFilteringTestSystem testSystem) throws Exception { + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, EXACTLY_EQUALS, valueToMatch)); FakeMail mail = testSystem.asMail(mimeMessageBuilder); testSystem.getJmapFiltering().service(mail); @@ -725,26 +726,26 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .conditionGroup(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox1Id.serialize()))) .build(), Rule.builder() .id(Rule.Id.of("2")) .name("rule 2") - .condition(Rule.Condition.of(FROM, NOT_CONTAINS, USER_1_USERNAME)) + .conditionGroup(Rule.Condition.of(FROM, NOT_CONTAINS, USER_1_USERNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox2Id.serialize()))) .build(), Rule.builder() .id(Rule.Id.of("3")) .name("rule 3") - .condition(Rule.Condition.of(TO, EXACTLY_EQUALS, USER_3_ADDRESS)) + .conditionGroup(Rule.Condition.of(TO, EXACTLY_EQUALS, USER_3_ADDRESS)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox3Id.serialize()))) .build())).block(); FakeMail mail = testSystem.asMail(mimeMessageBuilder() - .addFrom(USER_2_ADDRESS) - .addToRecipient(USER_3_ADDRESS) - .setSubject(UNSCRAMBLED_SUBJECT)); + .addFrom(USER_2_ADDRESS) + .addToRecipient(USER_3_ADDRESS) + .setSubject(UNSCRAMBLED_SUBJECT)); testSystem.getJmapFiltering().service(mail); @@ -763,7 +764,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .conditionGroup(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of( mailbox3Id.serialize(), mailbox2Id.serialize(), @@ -771,7 +772,7 @@ class JMAPFilteringTest { .build())).block(); FakeMail mail = testSystem.asMail(mimeMessageBuilder() - .setSubject(UNSCRAMBLED_SUBJECT)); + .setSubject(UNSCRAMBLED_SUBJECT)); testSystem.getJmapFiltering().service(mail); @@ -790,19 +791,19 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .conditionGroup(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of()))) .build(), Rule.builder() .id(Rule.Id.of("2")) .name("rule 2") - .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .conditionGroup(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of( mailbox1Id.serialize())))) .build())).block(); FakeMail mail = testSystem.asMail(mimeMessageBuilder() - .setSubject(UNSCRAMBLED_SUBJECT)); + .setSubject(UNSCRAMBLED_SUBJECT)); testSystem.getJmapFiltering().service(mail); @@ -839,9 +840,9 @@ class JMAPFilteringTest { Rule.Condition.of(CC, CONTAINS, SHOULD_NOT_MATCH)); FakeMail mail = testSystem.asMail(mimeMessageBuilder() - .addFrom(USER_1_FULL_ADDRESS) - .addToRecipient(USER_2_FULL_ADDRESS) - .addCcRecipient(USER_3_FULL_ADDRESS)); + .addFrom(USER_1_FULL_ADDRESS) + .addToRecipient(USER_2_FULL_ADDRESS) + .addCcRecipient(USER_3_FULL_ADDRESS)); testSystem.getJmapFiltering().service(mail); @@ -860,7 +861,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(unknownMailboxId))) .build())).block(); @@ -879,7 +880,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(unknownMailboxId))) .build())).block(); @@ -900,13 +901,13 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(unknownMailboxId))) .build(), Rule.builder() .id(Rule.Id.of("2")) .name("rule 2") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds( testSystem.getRecipient1MailboxId().serialize()))) .build())).block(); @@ -929,7 +930,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds( unknownMailboxId, testSystem.getRecipient1MailboxId().serialize()))) @@ -952,7 +953,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(), false, false, true, ImmutableList.of())) .build())).block(); @@ -972,7 +973,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(), true, false, false, ImmutableList.of())) .build())).block(); @@ -995,7 +996,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(), false, true, false, ImmutableList.of())) .build())).block(); @@ -1018,7 +1019,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(), false, false, false, ImmutableList.of("abc", "def"))) .build())).block(); @@ -1039,7 +1040,7 @@ class JMAPFilteringTest { Rule.builder() .id(Rule.Id.of("1")) .name("rule 1") - .condition(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) + .conditionGroup(Rule.Condition.of(FROM, CONTAINS, FRED_MARTIN_FULLNAME)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(), true, true, false, ImmutableList.of("abc", "def"))) .build())).block(); @@ -1057,4 +1058,75 @@ class JMAPFilteringTest { assertThat(StorageDirective.fromMail(Username.of("recipient1"), mail).getFlags().get()) .isEqualTo(expectedFlags); } + + @Test + void andShouldMatchWhenAllConditionsAreMet(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, Rule.Condition.of(FROM, CONTAINS, USER_2_USERNAME), Rule.Condition.of(SUBJECT, CONTAINS, "cd"))) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail mail = testSystem.asMail(mimeMessageBuilder().addHeader(FROM.asString(), USER_2_ADDRESS).addHeader(SUBJECT.asString(), "abcdef")); + + testSystem.getJmapFiltering().service(mail); + + assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); + } + + @Test + void andShouldNotMatchWhenSomeConditionsAreNotMet(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, Rule.Condition.of(FROM, CONTAINS, USER_2_USERNAME), Rule.Condition.of(SUBJECT, CONTAINS, "cdf"))) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail mail = testSystem.asMail(mimeMessageBuilder().addHeader(FROM.asString(), USER_2_ADDRESS).addHeader(SUBJECT.asString(), "abcdef")); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME).isEmpty()).isTrue(); + } + + @Test + void orShouldMatchWhenSomeConditionsAreMet(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.OR, Rule.Condition.of(FROM, CONTAINS, USER_2_USERNAME), Rule.Condition.of(SUBJECT, CONTAINS, "cdf"))) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail mail = testSystem.asMail(mimeMessageBuilder().addHeader(FROM.asString(), USER_2_ADDRESS).addHeader(SUBJECT.asString(), "abcdef")); + testSystem.getJmapFiltering().service(mail); + + assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); + } + + @Test + void orShouldNotMatchWhenNoConditionsAreMet(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.OR, Rule.Condition.of(FROM, CONTAINS, USER_2_USERNAME), Rule.Condition.of(SUBJECT, CONTAINS, "cdf"))) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail mail = testSystem.asMail(mimeMessageBuilder().addHeader(FROM.asString(), USER_3_ADDRESS).addHeader(SUBJECT.asString(), "abcdef")); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME).isEmpty()).isTrue(); + } } \ No newline at end of file diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java index 6fc450b308..ecada4e995 100644 --- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java @@ -244,7 +244,7 @@ class MemoryUserDeletionIntegrationTest { void shouldDeleteFilters(GuiceJamesServer server) { Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something"); Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01")); - Rule.Builder RULE_BUILDER = Rule.builder().name("A name").condition(CONDITION).action(ACTION); + Rule.Builder RULE_BUILDER = Rule.builder().name("A name").conditionGroup(CONDITION).action(ACTION); Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build(); server.getProbe(MemoryUserDeletionIntegrationTestProbe.class) .defineFilters(ALICE, ImmutableList.of(RULE_1)); diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java index bd0ea5e116..dc3e65741f 100644 --- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java @@ -108,7 +108,7 @@ class MemoryUsernameChangeIntegrationTest { private static final String NAME = "a name"; private static final Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something"); private static final Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01")); - private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).condition(CONDITION).action(ACTION); + private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).conditionGroup(CONDITION).action(ACTION); private static final Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build(); private static final Rule RULE_2 = RULE_BUILDER.id(Rule.Id.of("2")).build(); private static final Optional<Version> NO_VERSION = Optional.empty(); diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateFilteringProjectionRequestToTaskTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateFilteringProjectionRequestToTaskTest.java index 21546d1c5a..9e2171c266 100644 --- a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateFilteringProjectionRequestToTaskTest.java +++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateFilteringProjectionRequestToTaskTest.java @@ -210,7 +210,7 @@ class PopulateFilteringProjectionRequestToTaskTest { Rule rule = Rule.builder() .id(Rule.Id.of("2")) .name("rule 2") - .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .conditionGroup(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of(bobInboxboxId.serialize())))) .build(); --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org