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 f89c3ca5b1d659f87e6a29433bf26a0152979437
Author: Quan Tran <[email protected]>
AuthorDate: Wed Oct 1 12:32:11 2025 +0700

    JAMES-4148 MoveTo action should provision mailbox if needed
---
 .../james/mailets/FilterIntegrationTest.java       | 15 +++++
 .../james/jmap/mailet/filter/ActionApplier.java    | 29 ++++++---
 .../data/jmap/RunRulesOnMailboxService.java        | 10 ++-
 .../data/jmap/RunRulesOnMailboxRoutesTest.java     | 73 ++++++++++++++++++++++
 4 files changed, 119 insertions(+), 8 deletions(-)

diff --git 
a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java
 
b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java
index 6901446449..8b81c77146 100644
--- 
a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java
+++ 
b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java
@@ -266,6 +266,21 @@ public class FilterIntegrationTest {
                 .awaitMessageCount(Constants.awaitAtMostOneMinute, 1);
         }
 
+        @Test
+        void moveToShouldCreateMailboxWhenMailboxDoesNotExist() throws 
Exception {
+            filteringManagementProbe.defineRulesForUser(BOB, 
asRule(Action.builder()
+                .setMoveTo(Optional.of(new Action.MoveTo("customMailbox")))));
+
+            messageSender.connect(LOCALHOST_IP, 
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+                .authenticate(ALICE.asString(), PASSWORD)
+                .sendMessage(ALICE.asString(), BOB.asString());
+
+            imapClient.connect(LOCALHOST_IP, 
jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+                .login(BOB, PASSWORD)
+                .select("customMailbox")
+                .awaitMessageCount(Constants.awaitAtMostOneMinute, 1);
+        }
+
         @Test
         void moveToWithAppendInShouldWork() throws Exception {
             MailboxProbeImpl mailboxProbe = 
jamesServer.getProbe(MailboxProbeImpl.class);
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
index cca4fa0b00..adc6f86cb1 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
+++ 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
@@ -37,6 +37,8 @@ import org.apache.james.lifecycle.api.LifecycleUtil;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxExistsException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -229,19 +231,32 @@ public class ActionApplier {
     }
 
     private Stream<String> validateMailboxName(String mailboxName) {
+        MailboxPath mailboxPath = MailboxPath.forUser(username, mailboxName);
+        MailboxSession mailboxSession = 
mailboxManager.createSystemSession(username);
         try {
-            MailboxSession mailboxSession = 
mailboxManager.createSystemSession(username);
-            MessageManager messageManager = 
mailboxManager.getMailbox(MailboxPath.forUser(username, mailboxName), 
mailboxSession);
-            mailboxManager.endProcessingRequest(mailboxSession);
-
+            MessageManager messageManager = 
mailboxManager.getMailbox(mailboxPath, mailboxSession);
             return Stream.of(messageManager.getMailboxPath().getName());
         } catch (MailboxNotFoundException e) {
-            LOGGER.info("Mailbox {} does not exist for user {}, but it was 
mentioned in a JMAP filtering rule", mailboxName, username.asString(), e);
-            return Stream.empty();
-        } catch (Exception e) {
+            return createMailbox(mailboxName, mailboxPath, mailboxSession);
+        } catch (MailboxException e) {
             LOGGER.error("Unexpected failure while validating mailbox name {} 
for user {}", mailboxName, username.asString(), e);
             return Stream.empty();
+        } finally {
+            mailboxManager.endProcessingRequest(mailboxSession);
+        }
+    }
+
+    private Stream<String> createMailbox(String mailboxName, MailboxPath 
mailboxPath, MailboxSession mailboxSession) {
+        try {
+            return mailboxManager.createMailbox(mailboxPath, 
MailboxManager.CreateOption.CREATE_SUBSCRIPTION, mailboxSession)
+                .stream()
+                .map(createdMailbox -> mailboxName);
+        } catch (MailboxExistsException mailboxExistsException) {
+            LOGGER.info("Mailbox {} created by concurrent call", 
mailboxPath.asString());
+        } catch (MailboxException mailboxException) {
+            LOGGER.error("Failed to provision mailbox {}", 
mailboxPath.asString(), mailboxException);
         }
+        return Stream.empty();
     }
 
     private void sendACopy(LoopPrevention.RecordedRecipients 
recordedRecipients,
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
index 1a64ccfeb3..d80664a1ef 100644
--- 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
@@ -35,6 +35,8 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxExistsException;
+import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.FetchGroup;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -136,6 +138,12 @@ public class RunRulesOnMailboxService {
 
     private Mono<MailboxId> getMailboxId(MailboxSession mailboxSession, 
MailboxPath mailboxPath) {
         return Mono.from(mailboxManager.getMailboxReactive(mailboxPath, 
mailboxSession))
-            .map(MessageManager::getId);
+            .map(MessageManager::getId)
+            .onErrorResume(MailboxNotFoundException.class, e -> 
Mono.from(mailboxManager.createMailboxReactive(mailboxPath, 
MailboxManager.CreateOption.CREATE_SUBSCRIPTION, mailboxSession))
+                .onErrorResume(MailboxExistsException.class, e2 -> {
+                    LOGGER.info("Mailbox {} created concurrently", 
mailboxPath.asString());
+                    return 
Mono.from(mailboxManager.getMailboxReactive(mailboxPath, mailboxSession))
+                        .map(MessageManager::getId);
+                }));
     }
 }
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 2950d2b5f8..e59e6825a9 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
@@ -386,6 +386,79 @@ public class RunRulesOnMailboxRoutesTest {
         );
     }
 
+    @Test
+    void 
runRulesOnMailboxShouldSupportMoveToMailboxWhenMatchingMessageAndTargetMailboxDoesNotExist()
 throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath moveToMailboxPath = MailboxPath.forUser(USERNAME, 
MOVE_TO_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+
+        mailboxManager.getMailbox(mailboxPath, systemSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(Message.Builder.of()
+                        .setSubject("plop")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+                {
+                  "id": "1",
+                  "name": "rule 1",
+                  "action": {
+                    "appendIn": {
+                      "mailboxIds": []
+                    },
+                    "moveTo": {
+                      "mailboxName": "%s"
+                    },
+                    "important": false,
+                    "keyworkds": [],
+                    "reject": false,
+                    "seen": false
+                  },
+                  "conditionGroup": {
+                    "conditionCombiner": "OR",
+                    "conditions": [
+                      {
+                        "comparator": "contains",
+                        "field": "subject",
+                        "value": "plop"
+                      },
+                      {
+                        "comparator": "exactly-equals",
+                        "field": "from",
+                        "value": "[email protected]"
+                      }
+                    ]
+                  }
+                }"""
+                .formatted(MOVE_TO_MAILBOX_NAME))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+            .when()
+            .get(taskId + "/await");
+
+        SoftAssertions.assertSoftly(
+            softly -> {
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(mailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(0);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(moveToMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+            }
+        );
+    }
+
     @Test
     void runRulesOnMailboxShouldNotMoveToMailboxNameWhenNonMatchingMessage() 
throws Exception {
         MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);


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

Reply via email to