You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2025/11/25 06:21:53 UTC
(james-project) 12/34: JAMES-4148 Apply maximum number of actions taken per mailbox (#2836)
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 58013510fa8d6573b46deb2a8d740c94d8f9cbe1
Author: Trần Hồng Quân <55...@users.noreply.github.com>
AuthorDate: Tue Oct 14 14:19:10 2025 +0700
JAMES-4148 Apply maximum number of actions taken per mailbox (#2836)
Avoid moving too many messages at once which could cause tombstone issue on Cassandra.
---
.../modules/servers/partials/operate/webadmin.adoc | 5 +
.../data/jmap/RunRulesOnMailboxService.java | 38 ++++-
.../webadmin/data/jmap/RunRulesOnMailboxTask.java | 60 ++++++--
...RulesOnMailboxTaskAdditionalInformationDTO.java | 24 +++-
.../data/jmap/RunRulesOnMailboxRoutesTest.java | 158 ++++++++++++++++++++-
...sOnMailboxTaskAdditionalInformationDTOTest.java | 2 +-
.../runRulesOnMailbox.additionalInformation.json | 2 +
7 files changed, 273 insertions(+), 16 deletions(-)
diff --git a/docs/modules/servers/partials/operate/webadmin.adoc b/docs/modules/servers/partials/operate/webadmin.adoc
index 36e5d7d985..ccf1fdf780 100644
--- a/docs/modules/servers/partials/operate/webadmin.adoc
+++ b/docs/modules/servers/partials/operate/webadmin.adoc
@@ -1864,14 +1864,19 @@ the following `additionalInformation`:
....
{
"mailboxName": "mbx1",
+ "processedMessagesCount": 15,
"rulesOnMessagesApplySuccessfully": 9,
"rulesOnMessagesApplyFailed": 3,
+ "maximumAppliedActionExceeded": false,
"timestamp": "2024-12-03T10:15:30Z",
"type": "RunRulesOnMailboxTask",
"username": "bob@domain.tld"
}
....
+Note: you can configure the maximum number of actions applied per mailbox by setting the JVM property `james.rules.triage.max.actions.per.mailbox`. The default value is 25000.
+This limit may help avoid the tombstone threshold issue on Cassandra backends when too many messages are moved or deleted at once.
+
=== Subscribing a user to all of its mailboxes
....
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 d80664a1ef..881dc3fefb 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
@@ -22,6 +22,8 @@ package org.apache.james.webadmin.data.jmap;
import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
import jakarta.inject.Inject;
@@ -49,23 +51,39 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.fge.lambdas.Throwing;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import reactor.core.publisher.SynchronousSink;
public class RunRulesOnMailboxService {
+ public static class TooManyAppliedActionsException extends RuntimeException {
+ }
+
private static final Logger LOGGER = LoggerFactory.getLogger(RunRulesOnMailboxService.class);
+ private static final int MAX_ACTIONS_PER_MAILBOX = Optional.ofNullable(System.getProperty("james.rules.triage.max.actions.per.mailbox"))
+ .map(Integer::valueOf)
+ .orElse(25000);
private final MailboxManager mailboxManager;
private final MailboxId.Factory mailboxIdFactory;
private final MessageIdManager messageIdManager;
+ private final int maxActionsPerMailbox;
@Inject
public RunRulesOnMailboxService(MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory, MessageIdManager messageIdManager) {
+ this(mailboxManager, mailboxIdFactory, messageIdManager, MAX_ACTIONS_PER_MAILBOX);
+ }
+
+ @VisibleForTesting
+ public RunRulesOnMailboxService(MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory, MessageIdManager messageIdManager,
+ int maxActionsPerMailbox) {
this.mailboxManager = mailboxManager;
this.mailboxIdFactory = mailboxIdFactory;
this.messageIdManager = messageIdManager;
+ this.maxActionsPerMailbox = maxActionsPerMailbox;
}
public Mono<Task.Result> runRulesOnMailbox(Username username, MailboxName mailboxName, Rules rules, RunRulesOnMailboxTask.Context context) {
@@ -75,6 +93,11 @@ public class RunRulesOnMailboxService {
return Mono.from(mailboxManager.getMailboxReactive(MailboxPath.forUser(username, mailboxName.asString()), mailboxSession))
.flatMapMany(messageManager -> Flux.from(messageManager.getMessagesReactive(MessageRange.all(), FetchGroup.HEADERS, mailboxSession))
.flatMap(Throwing.function(messageResult -> runRulesOnMessage(ruleMatcher, messageResult, mailboxSession, context)), DEFAULT_CONCURRENCY))
+ .onErrorResume(TooManyAppliedActionsException.class, e -> {
+ LOGGER.info("Maximum number of actions exceeded for mailbox {} of user {}", mailboxName.asString(), username);
+ context.setMaximumAppliedActionExceeded();
+ return Mono.just(Task.Result.COMPLETED);
+ })
.onErrorResume(e -> {
LOGGER.error("Error when applying rules to mailbox. Mailbox {} for user {}", mailboxName.asString(), username, e);
context.incrementFails();
@@ -86,11 +109,24 @@ public class RunRulesOnMailboxService {
}
private Flux<Task.Result> runRulesOnMessage(RuleMatcher ruleMatcher, MessageResult messageResult, MailboxSession mailboxSession, RunRulesOnMailboxTask.Context context) throws MailboxException {
+ context.increaseProcessedMessagesCount();
+
return Flux.fromStream(ruleMatcher.findApplicableRules(messageResult))
.map(Rule::getAction)
+ .handle(applyMaxActionsLimit(context))
.concatMap(action -> applyActionOnMessage(messageResult, action, mailboxSession, context));
}
+ private BiConsumer<Rule.Action, SynchronousSink<Rule.Action>> applyMaxActionsLimit(RunRulesOnMailboxTask.Context context) {
+ return (action, sink) -> {
+ if (context.snapshot().getRulesOnMessagesApplySuccessfully() >= maxActionsPerMailbox) {
+ sink.error(new TooManyAppliedActionsException());
+ } else {
+ sink.next(action);
+ }
+ };
+ }
+
private Mono<Task.Result> applyActionOnMessage(MessageResult messageResult, Rule.Action action, MailboxSession mailboxSession, RunRulesOnMailboxTask.Context context) {
actionOnMessagePreconditions(action);
return appendInMailboxes(messageResult.getMessageId(), action, mailboxSession, context)
@@ -131,7 +167,7 @@ public class RunRulesOnMailboxService {
}
return Mono.from(messageIdManager.setInMailboxesReactive(messageId, mailboxIds, mailboxSession))
- .doOnSuccess(next -> context.incrementSuccesses())
+ .then(Mono.fromRunnable(context::incrementSuccesses))
.then(Mono.just(Task.Result.COMPLETED));
});
}
diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
index 0b3f30be1d..e9c3f7f356 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
@@ -23,6 +23,7 @@ import java.time.Clock;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.james.core.Username;
@@ -39,10 +40,14 @@ public class RunRulesOnMailboxTask implements Task {
public static class Snapshot {
private final long rulesOnMessagesApplySuccessfully;
private final long rulesOnMessagesApplyFailed;
+ private final boolean maximumAppliedActionExceeded;
+ private final long processedMessagesCount;
- private Snapshot(long rulesOnMessagesApplySuccessfully, long rulesOnMessagesApplyFailed) {
+ private Snapshot(long rulesOnMessagesApplySuccessfully, long rulesOnMessagesApplyFailed, boolean maximumAppliedActionExceeded, long processedMessagesCount) {
this.rulesOnMessagesApplySuccessfully = rulesOnMessagesApplySuccessfully;
this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+ this.maximumAppliedActionExceeded = maximumAppliedActionExceeded;
+ this.processedMessagesCount = processedMessagesCount;
}
public long getRulesOnMessagesApplySuccessfully() {
@@ -59,14 +64,17 @@ public class RunRulesOnMailboxTask implements Task {
Context.Snapshot that = (Context.Snapshot) o;
return Objects.equals(this.rulesOnMessagesApplySuccessfully, that.rulesOnMessagesApplySuccessfully)
- && Objects.equals(this.rulesOnMessagesApplyFailed, that.rulesOnMessagesApplyFailed);
+ && Objects.equals(this.rulesOnMessagesApplyFailed, that.rulesOnMessagesApplyFailed)
+ && Objects.equals(this.maximumAppliedActionExceeded, that.maximumAppliedActionExceeded)
+ && Objects.equals(this.processedMessagesCount, that.processedMessagesCount);
}
return false;
}
@Override
public final int hashCode() {
- return Objects.hash(rulesOnMessagesApplySuccessfully, rulesOnMessagesApplyFailed);
+ return Objects.hash(rulesOnMessagesApplySuccessfully, rulesOnMessagesApplyFailed, maximumAppliedActionExceeded,
+ processedMessagesCount);
}
@Override
@@ -74,44 +82,62 @@ public class RunRulesOnMailboxTask implements Task {
return MoreObjects.toStringHelper(this)
.add("rulesOnMessagesApplySuccessfully", rulesOnMessagesApplySuccessfully)
.add("rulesOnMessagesApplyFailed", rulesOnMessagesApplyFailed)
+ .add("maximumAppliedActionExceeded", maximumAppliedActionExceeded)
+ .add("processedMessagesCount", processedMessagesCount)
.toString();
}
}
private final AtomicLong rulesOnMessagesApplySuccessfully;
private final AtomicLong rulesOnMessagesApplyFailed;
+ private final AtomicBoolean maximumAppliedActionExceeded;
+ private final AtomicLong processedMessagesCount;
public Context() {
this.rulesOnMessagesApplySuccessfully = new AtomicLong();
this.rulesOnMessagesApplyFailed = new AtomicLong();
+ this.maximumAppliedActionExceeded = new AtomicBoolean();
+ this.processedMessagesCount = new AtomicLong();
}
- public Context(long rulesOnMessagesApplySuccessfully, long rulesOnMessagesApplyFailed) {
+ public Context(long rulesOnMessagesApplySuccessfully, long rulesOnMessagesApplyFailed, boolean maximumAppliedActionExceeded,
+ long processedMessagesCount) {
this.rulesOnMessagesApplySuccessfully = new AtomicLong(rulesOnMessagesApplySuccessfully);
this.rulesOnMessagesApplyFailed = new AtomicLong(rulesOnMessagesApplyFailed);
+ this.maximumAppliedActionExceeded = new AtomicBoolean(maximumAppliedActionExceeded);
+ this.processedMessagesCount = new AtomicLong(processedMessagesCount);
}
public void incrementSuccesses() {
rulesOnMessagesApplySuccessfully.incrementAndGet();
}
-
public void incrementFails() {
rulesOnMessagesApplyFailed.incrementAndGet();
}
+ public void setMaximumAppliedActionExceeded() {
+ maximumAppliedActionExceeded.set(true);
+ }
+
+ public void increaseProcessedMessagesCount() {
+ processedMessagesCount.incrementAndGet();
+ }
+
public Context.Snapshot snapshot() {
- return new Context.Snapshot(rulesOnMessagesApplySuccessfully.get(), rulesOnMessagesApplyFailed.get());
+ return new Context.Snapshot(rulesOnMessagesApplySuccessfully.get(), rulesOnMessagesApplyFailed.get(), maximumAppliedActionExceeded.get(),
+ processedMessagesCount.get());
}
}
public static class AdditionalInformation implements TaskExecutionDetails.AdditionalInformation {
private static AdditionalInformation from(Username username,
- MailboxName mailboxName,
- RunRulesOnMailboxTask.Context context) {
+ MailboxName mailboxName,
+ RunRulesOnMailboxTask.Context context) {
Context.Snapshot snapshot = context.snapshot();
- return new AdditionalInformation(username, mailboxName, Clock.systemUTC().instant(), snapshot.rulesOnMessagesApplySuccessfully, snapshot.rulesOnMessagesApplyFailed);
+ return new AdditionalInformation(username, mailboxName, Clock.systemUTC().instant(), snapshot.rulesOnMessagesApplySuccessfully,
+ snapshot.rulesOnMessagesApplyFailed, snapshot.maximumAppliedActionExceeded, snapshot.processedMessagesCount);
}
private final Username username;
@@ -119,17 +145,23 @@ public class RunRulesOnMailboxTask implements Task {
private final Instant timestamp;
private final long rulesOnMessagesApplySuccessfully;
private final long rulesOnMessagesApplyFailed;
+ private final boolean maximumAppliedActionExceeded;
+ private final long processedMessagesCount;
public AdditionalInformation(Username username,
MailboxName mailboxName,
Instant timestamp,
long rulesOnMessagesApplySuccessfully,
- long rulesOnMessagesApplyFailed) {
+ long rulesOnMessagesApplyFailed,
+ boolean maximumAppliedActionExceeded,
+ long processedMessagesCount) {
this.username = username;
this.mailboxName = mailboxName;
this.timestamp = timestamp;
this.rulesOnMessagesApplySuccessfully = rulesOnMessagesApplySuccessfully;
this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+ this.maximumAppliedActionExceeded = maximumAppliedActionExceeded;
+ this.processedMessagesCount = processedMessagesCount;
}
public Username getUsername() {
@@ -152,6 +184,14 @@ public class RunRulesOnMailboxTask implements Task {
return rulesOnMessagesApplyFailed;
}
+ public boolean maximumAppliedActionExceeded() {
+ return maximumAppliedActionExceeded;
+ }
+
+ public long getProcessedMessagesCount() {
+ return processedMessagesCount;
+ }
+
@Override
public Instant timestamp() {
return timestamp;
diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
index a3b101f23e..4f3feff328 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
@@ -44,7 +44,9 @@ public class RunRulesOnMailboxTaskAdditionalInformationDTO implements Additional
new MailboxName(dto.getMailboxName()),
dto.getTimestamp(),
dto.getRulesOnMessagesApplySuccessfully(),
- dto.getRulesOnMessagesApplyFailed());
+ dto.getRulesOnMessagesApplyFailed(),
+ dto.getMaximumAppliedActionExceeded(),
+ dto.getProcessedMessagesCount());
}
private static RunRulesOnMailboxTaskAdditionalInformationDTO toDto(RunRulesOnMailboxTask.AdditionalInformation domain, String type) {
@@ -54,7 +56,9 @@ public class RunRulesOnMailboxTaskAdditionalInformationDTO implements Additional
domain.getMailboxName().asString(),
domain.getTimestamp(),
domain.getRulesOnMessagesApplySuccessfully(),
- domain.getRulesOnMessagesApplyFailed());
+ domain.getRulesOnMessagesApplyFailed(),
+ domain.maximumAppliedActionExceeded(),
+ domain.getProcessedMessagesCount());
}
private final String type;
@@ -63,19 +67,25 @@ public class RunRulesOnMailboxTaskAdditionalInformationDTO implements Additional
private final Instant timestamp;
private final long rulesOnMessagesApplySuccessfully;
private final long rulesOnMessagesApplyFailed;
+ private final boolean maximumAppliedActionExceeded;
+ private final long processedMessagesCount;
public RunRulesOnMailboxTaskAdditionalInformationDTO(@JsonProperty("type") String type,
@JsonProperty("username") String username,
@JsonProperty("mailboxName") String mailboxName,
@JsonProperty("timestamp") Instant timestamp,
@JsonProperty("rulesOnMessagesApplySuccessfully") long rulesOnMessagesApplySuccessfully,
- @JsonProperty("rulesOnMessagesApplyFailed") long rulesOnMessagesApplyFailed) {
+ @JsonProperty("rulesOnMessagesApplyFailed") long rulesOnMessagesApplyFailed,
+ @JsonProperty("maximumAppliedActionExceeded") boolean maximumAppliedActionExceeded,
+ @JsonProperty("processedMessagesCount") long processedMessagesCount) {
this.type = type;
this.username = username;
this.mailboxName = mailboxName;
this.timestamp = timestamp;
this.rulesOnMessagesApplySuccessfully = rulesOnMessagesApplySuccessfully;
this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+ this.maximumAppliedActionExceeded = maximumAppliedActionExceeded;
+ this.processedMessagesCount = processedMessagesCount;
}
@Override
@@ -103,4 +113,12 @@ public class RunRulesOnMailboxTaskAdditionalInformationDTO implements Additional
public long getRulesOnMessagesApplyFailed() {
return rulesOnMessagesApplyFailed;
}
+
+ public boolean getMaximumAppliedActionExceeded() {
+ return maximumAppliedActionExceeded;
+ }
+
+ public long getProcessedMessagesCount() {
+ return processedMessagesCount;
+ }
}
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 3d36c605f8..1111ea9d97 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
@@ -36,6 +36,7 @@ import java.time.Clock;
import java.time.Duration;
import java.util.Date;
import java.util.Map;
+import java.util.stream.IntStream;
import jakarta.mail.Flags;
@@ -1039,6 +1040,161 @@ public class RunRulesOnMailboxRoutesTest {
.body("additionalInformation.username", Matchers.is(USERNAME.asString()))
.body("additionalInformation.mailboxName", Matchers.is(MAILBOX_NAME))
.body("additionalInformation.rulesOnMessagesApplySuccessfully", Matchers.is(2))
- .body("additionalInformation.rulesOnMessagesApplyFailed", Matchers.is(0));
+ .body("additionalInformation.rulesOnMessagesApplyFailed", Matchers.is(0))
+ .body("additionalInformation.maximumAppliedActionExceeded", Matchers.is(false))
+ .body("additionalInformation.processedMessagesCount", Matchers.is(3));
+ }
+
+ @Test
+ void taskShouldStopAndCompleteWhenAppliedActionsExceedMaximumLimit() throws Exception {
+ overrideTriageRulesRouteWithActionsLimit(2);
+
+ MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+ MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, OTHER_MAILBOX_NAME);
+ MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+
+ mailboxManager.createMailbox(mailboxPath, systemSession);
+ mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+ MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, systemSession);
+
+ // Add 20 matching messages, which exceeds the max actions of 2
+ IntStream.range(0, 20)
+ .forEach(Throwing.intConsumer(i -> messageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .build(Message.Builder.of()
+ .setSubject("plop")
+ .setFrom("alice@example.com")
+ .setBody("matched mail", StandardCharsets.UTF_8)),
+ systemSession)));
+
+ MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, systemSession).getId();
+
+ String taskId = given()
+ .queryParam("action", "triage")
+ .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+ .post(MAILBOX_NAME + "/messages")
+ .then()
+ .statusCode(CREATED_201)
+ .extract()
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", Matchers.is("completed"))
+ .body("additionalInformation.rulesOnMessagesApplySuccessfully", Matchers.lessThan(20))
+ .body("additionalInformation.rulesOnMessagesApplyFailed", Matchers.is(0))
+ .body("additionalInformation.maximumAppliedActionExceeded", Matchers.is(true));
+ }
+
+ @Test
+ void taskShouldCompleteWhenAppliedActionsReachMaximumLimit() throws Exception {
+ overrideTriageRulesRouteWithActionsLimit(2);
+
+ MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+ MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, OTHER_MAILBOX_NAME);
+ MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+
+ mailboxManager.createMailbox(mailboxPath, systemSession);
+ mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+ MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, systemSession);
+
+ // Add 2 matching messages, reach the limit of 2
+ IntStream.range(0, 2)
+ .forEach(Throwing.intConsumer(i -> messageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .build(Message.Builder.of()
+ .setSubject("plop")
+ .setFrom("alice@example.com")
+ .setBody("matched mail", StandardCharsets.UTF_8)),
+ systemSession)));
+
+ MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, systemSession).getId();
+
+ String taskId = given()
+ .queryParam("action", "triage")
+ .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+ .post(MAILBOX_NAME + "/messages")
+ .then()
+ .statusCode(CREATED_201)
+ .extract()
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", Matchers.is("completed"))
+ .body("additionalInformation.rulesOnMessagesApplySuccessfully", Matchers.is(2))
+ .body("additionalInformation.rulesOnMessagesApplyFailed", Matchers.is(0))
+ .body("additionalInformation.maximumAppliedActionExceeded", Matchers.is(false));
+ }
+
+ @Test
+ void taskShouldCompleteWhenAppliedActionsLessThanMaximumLimit() throws Exception {
+ overrideTriageRulesRouteWithActionsLimit(10);
+
+ MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+ MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, OTHER_MAILBOX_NAME);
+ MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+
+ mailboxManager.createMailbox(mailboxPath, systemSession);
+ mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+ MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, systemSession);
+
+ // Add 2 matching messages, < the limit of 10
+ IntStream.range(0, 2)
+ .forEach(Throwing.intConsumer(i -> messageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .build(Message.Builder.of()
+ .setSubject("plop")
+ .setFrom("alice@example.com")
+ .setBody("matched mail", StandardCharsets.UTF_8)),
+ systemSession)));
+
+ MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, systemSession).getId();
+
+ String taskId = given()
+ .queryParam("action", "triage")
+ .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+ .post(MAILBOX_NAME + "/messages")
+ .then()
+ .statusCode(CREATED_201)
+ .extract()
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", Matchers.is("completed"))
+ .body("additionalInformation.rulesOnMessagesApplySuccessfully", Matchers.is(2))
+ .body("additionalInformation.rulesOnMessagesApplyFailed", Matchers.is(0))
+ .body("additionalInformation.maximumAppliedActionExceeded", Matchers.is(false));
+ }
+
+ private void overrideTriageRulesRouteWithActionsLimit(int maxActionsLimit) {
+ webAdminServer.destroy();
+ webAdminServer = WebAdminUtils.createWebAdminServer(
+ new RunRulesOnMailboxRoutes(usersRepository, mailboxManager, taskManager, new JsonTransformer(),
+ new RunRulesOnMailboxService(mailboxManager, new InMemoryId.Factory(), messageIdManager, maxActionsLimit)),
+ new TasksRoutes(taskManager, new JsonTransformer(),
+ DTOConverter.of(RunRulesOnMailboxTaskAdditionalInformationDTO.SERIALIZATION_MODULE)))
+ .start();
+
+ RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+ .setBasePath(USERS_BASE + SEPARATOR + USERNAME.asString() + SEPARATOR + UserMailboxesRoutes.MAILBOXES)
+ .setUrlEncodingEnabled(false)
+ .build();
}
}
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
index 0ce8572d5a..b6cfcc7a35 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
@@ -31,7 +31,7 @@ public class RunRulesOnMailboxTaskAdditionalInformationDTOTest {
private static final Instant INSTANT = Instant.parse("2007-12-03T10:15:30.00Z");
private static final RunRulesOnMailboxTask.AdditionalInformation DOMAIN_OBJECT = new RunRulesOnMailboxTask.AdditionalInformation(
- Username.of("bob@domain.tld"), new MailboxName("mbx1"), INSTANT, 10, 9);
+ Username.of("bob@domain.tld"), new MailboxName("mbx1"), INSTANT, 10, 9, false, 30);
@Test
void shouldMatchJsonSerializationContract() throws Exception {
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
index a3f8bd738b..6be4f207e4 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
@@ -2,6 +2,8 @@
"mailboxName": "mbx1",
"rulesOnMessagesApplyFailed": 9,
"rulesOnMessagesApplySuccessfully": 10,
+ "processedMessagesCount": 30,
+ "maximumAppliedActionExceeded": false,
"timestamp": "2007-12-03T10:15:30Z",
"type": "RunRulesOnMailboxTask",
"username": "bob@domain.tld"
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org