This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch 3.9.x
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 1d7ab8ee5304daf6144e381934e9e06089290fcb
Author: Trần Hồng Quân <[email protected]>
AuthorDate: Fri Oct 3 14:09:03 2025 +0700

    JAMES-4148 Implement ANY comparator for custom header (#2832)
---
 .../src/test/resources/json/event-v4.json          |  5 ++
 .../org/apache/james/jmap/api/filtering/Rule.java  |  3 +-
 .../james/jmap/api/filtering/RuleFixture.java      |  3 +-
 .../james/jmap/mailet/filter/ContentMatcher.java   |  2 +
 .../jmap/mailet/filter/JMAPFilteringTest.java      | 66 ++++++++++++++++++++++
 .../data/jmap/RunRulesOnMailboxRoutesTest.java     | 15 ++---
 6 files changed, 83 insertions(+), 11 deletions(-)

diff --git 
a/server/data/data-jmap-cassandra/src/test/resources/json/event-v4.json 
b/server/data/data-jmap-cassandra/src/test/resources/json/event-v4.json
index bb54fd57fa..63264aec34 100644
--- a/server/data/data-jmap-cassandra/src/test/resources/json/event-v4.json
+++ b/server/data/data-jmap-cassandra/src/test/resources/json/event-v4.json
@@ -18,6 +18,11 @@
             "field": "header:custom",
             "comparator": "contains",
             "value": "another thing"
+          },
+          {
+            "field": "header:anotherCustom",
+            "comparator": "any",
+            "value": "disregard me"
           }
         ]
       },
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 bd64c8fb56..067f8b8a6a 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
@@ -189,7 +189,8 @@ public class Rule {
             NOT_EXACTLY_EQUALS("not-exactly-equals"),
             START_WITH("start-with"),
             IS_SET("isSet"),
-            IS_UNSET("isUnset");
+            IS_UNSET("isUnset"),
+            ANY("any");
             
             public static Optional<Comparator> find(String comparatorName) {
                 return Arrays.stream(values())
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 3b946e3758..c3a93a5603 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
@@ -57,7 +57,8 @@ public interface RuleFixture {
     Rule RULE_3 = RULE_BUILDER.id(Rule.Id.of("3")).build();
     Rule RULE_4 = Rule.builder()
         .conditionGroup(Rule.ConditionCombiner.AND, CONDITION,
-            Rule.Condition.of(new Rule.Condition.CustomHeaderField("custom"), 
Rule.Condition.Comparator.CONTAINS, "another thing"))
+            Rule.Condition.of(new Rule.Condition.CustomHeaderField("custom"), 
Rule.Condition.Comparator.CONTAINS, "another thing"),
+            Rule.Condition.of(new 
Rule.Condition.CustomHeaderField("anotherCustom"), 
Rule.Condition.Comparator.ANY, "disregard me"))
         .action(ACTION_2)
         .id(Rule.Id.of("1"))
         .name(NAME)
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
index 4ca7e5ca55..f0b55e3569 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
+++ 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
@@ -167,6 +167,7 @@ public interface ContentMatcher {
     ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> 
contents.anyMatch(content -> StringUtils.equals(content, valueToMatch));
     ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = 
negate(STRING_EXACTLY_EQUALS_MATCHER);
     ContentMatcher STRING_START_WITH_MATCHER = (contents, valueToMatch) -> 
contents.anyMatch(content -> content.startsWith(valueToMatch));
+    ContentMatcher ANY_MATCHER = (contents, valueToMatch) -> 
contents.anyMatch(StringUtils::isNotBlank);
 
     ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> 
contents
         .map(ContentMatcher::asAddressHeader)
@@ -201,6 +202,7 @@ public interface ContentMatcher {
         .put(Rule.Condition.Comparator.EXACTLY_EQUALS, 
STRING_EXACTLY_EQUALS_MATCHER)
         .put(Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, 
STRING_NOT_EXACTLY_EQUALS_MATCHER)
         .put(Rule.Condition.Comparator.START_WITH, STRING_START_WITH_MATCHER)
+        .put(Rule.Condition.Comparator.ANY, ANY_MATCHER)
         .build();
 
     Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> 
CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, 
Map<Rule.Condition.Comparator, ContentMatcher>>builder()
diff --git 
a/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
 
b/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
index 7ea7ed1a5c..937c6fe14d 100644
--- 
a/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
+++ 
b/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.jmap.mailet.filter;
 
 import static 
org.apache.james.core.builder.MimeMessageBuilder.mimeMessageBuilder;
+import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.ANY;
 import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS;
 import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.EXACTLY_EQUALS;
 import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.IS_NEWER_THAN;
@@ -1191,6 +1192,71 @@ class JMAPFilteringTest {
             .isEmpty();
     }
 
+    @Test
+    void customHeaderShouldSupportAnyComparator(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.Condition.of(Rule.Condition.CustomHeaderField.find("header:X-Github").get(),
 ANY, "disregarded"))
+                
.action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize())))
+                .build())).block();
+
+        FakeMail matchMail = testSystem.asMail(mimeMessageBuilder()
+            .addHeader(FROM.asString(), USER_2_ADDRESS)
+            .addHeader(SUBJECT.asString(), "subject")
+            .addHeader("X-Github", "I am Github header"));
+
+        testSystem.getJmapFiltering().service(matchMail);
+
+        
assertThatAttribute(matchMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+    }
+
+    @Test
+    void 
nonExistingCustomHeaderShouldNotMatchAnyComparator(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.Condition.of(Rule.Condition.CustomHeaderField.find("header:X-Github").get(),
 ANY, "disregarded"))
+                
.action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize())))
+                .build())).block();
+
+        FakeMail nonMatchMail = testSystem.asMail(mimeMessageBuilder()
+            .addHeader(FROM.asString(), USER_2_ADDRESS)
+            .addHeader(SUBJECT.asString(), "subject"));
+
+        testSystem.getJmapFiltering().service(nonMatchMail);
+
+        
assertThat(nonMatchMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEmpty();
+    }
+
+    @Test
+    void emptyCustomHeaderShouldNotMatchAnyComparator(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.Condition.of(Rule.Condition.CustomHeaderField.find("header:X-Github").get(),
 ANY, "disregarded"))
+                
.action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize())))
+                .build())).block();
+
+        FakeMail nonMatchMail = testSystem.asMail(mimeMessageBuilder()
+            .addHeader(FROM.asString(), USER_2_ADDRESS)
+            .addHeader(SUBJECT.asString(), "subject")
+            .addHeader("X-Github", ""));
+
+        testSystem.getJmapFiltering().service(nonMatchMail);
+
+        
assertThat(nonMatchMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEmpty();
+    }
+
     @Test
     void orShouldNotMatchWhenNoConditionsAreMet(JMAPFilteringTestSystem 
testSystem) throws Exception {
         
Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME,
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
index f65955ecc6..3d36c605f8 100644
--- 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
@@ -52,6 +52,7 @@ import 
org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.stream.RawField;
 import org.apache.james.task.Hostname;
 import org.apache.james.task.MemoryTaskManager;
 import org.apache.james.user.api.UsersRepository;
@@ -329,7 +330,8 @@ public class RunRulesOnMailboxRoutesTest {
                     .build(Message.Builder.of()
                         .setSubject("plop")
                         .setFrom("[email protected]")
-                        .setBody("body", StandardCharsets.UTF_8)),
+                        .setBody("body", StandardCharsets.UTF_8)
+                        .addField(new RawField("X-Custom-Header", "value"))),
                 systemSession);
 
         String taskId = given()
@@ -354,14 +356,9 @@ public class RunRulesOnMailboxRoutesTest {
                     "conditionCombiner": "OR",
                     "conditions": [
                       {
-                        "comparator": "contains",
-                        "field": "subject",
-                        "value": "plop"
-                      },
-                      {
-                        "comparator": "exactly-equals",
-                        "field": "from",
-                        "value": "[email protected]"
+                        "comparator": "any",
+                        "field": "header:X-Custom-Header",
+                        "value": "disregarded"
                       }
                     ]
                   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to