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 2023/05/26 09:33:57 UTC

[james-project] 02/02: DeletedMessagesVault API - support limit query

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

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

commit 6d3033b36288a502243e7f256f6a708d4c615085
Author: Tung Tran <vt...@linagora.com>
AuthorDate: Wed May 24 11:21:13 2023 +0700

    DeletedMessagesVault API - support limit query
---
 .../vault/blob/BlobStoreDeletedMessageVault.java   |  5 ++-
 .../org/apache/james/vault/dto/query/QueryDTO.java | 23 +++++++++--
 .../james/vault/dto/query/QueryTranslator.java     |  4 +-
 .../java/org/apache/james/vault/search/Query.java  | 23 +++++++++++
 .../james/vault/DeletedMessageVaultContract.java   | 16 ++++++++
 .../dto/query/QueryElementSerializerTest.java      |  7 +++-
 .../james/vault/dto/query/QueryTranslatorTest.java | 19 +++++++++-
 .../docs/modules/ROOT/pages/operate/webadmin.adoc  | 10 +++++
 .../vault/DeletedMessageVaultIntegrationTest.java  | 26 +++++++++++++
 .../routes/DeletedMessagesVaultRoutesTest.java     | 44 ++++++++++++++++++++++
 .../WebadminApiQuerySerializationContractTest.java | 23 +++++------
 src/site/markdown/server/manage-webadmin.md        |  9 +++++
 12 files changed, 188 insertions(+), 21 deletions(-)

diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
index c1b7a2ddd8..c3bfbf13c8 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
@@ -139,10 +139,13 @@ public class BlobStoreDeletedMessageVault implements DeletedMessageVault {
     }
 
     private Flux<DeletedMessage> searchOn(Username username, Query query) {
-        return Flux.from(messageMetadataVault.listRelatedBuckets())
+        Flux<DeletedMessage> filterPublisher = Flux.from(messageMetadataVault.listRelatedBuckets())
             .concatMap(bucketName -> messageMetadataVault.listMessages(bucketName, username))
             .map(DeletedMessageWithStorageInformation::getDeletedMessage)
             .filter(query.toPredicate());
+        return query.getLimit()
+            .map(filterPublisher::take)
+            .orElse(filterPublisher);
     }
 
     @Override
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryDTO.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryDTO.java
index f72ff0ccae..7f56275ec6 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryDTO.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryDTO.java
@@ -21,6 +21,7 @@ package org.apache.james.vault.dto.query;
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
 import org.apache.james.vault.search.Combinator;
 
@@ -35,16 +36,25 @@ public class QueryDTO implements QueryElement {
 
     @VisibleForTesting
     static QueryDTO and(QueryElement... queryElements) {
-        return new QueryDTO(Combinator.AND.getValue(), ImmutableList.copyOf(queryElements));
+        return new QueryDTO(Combinator.AND.getValue(), ImmutableList.copyOf(queryElements), Optional.empty());
+    }
+
+    @VisibleForTesting
+    static QueryDTO and(Long limit, QueryElement... queryElements) {
+        return new QueryDTO(Combinator.AND.getValue(), ImmutableList.copyOf(queryElements), Optional.ofNullable(limit));
     }
 
     private final String combinator;
     private final List<QueryElement> criteria;
+    private final Optional<Long> limit;
 
     @JsonCreator
-    public QueryDTO(@JsonProperty("combinator") String combinator, @JsonProperty("criteria") List<QueryElement> criteria) {
+    public QueryDTO(@JsonProperty("combinator") String combinator,
+                    @JsonProperty("criteria") List<QueryElement> criteria,
+                    @JsonProperty("limit") Optional<Long> limit) {
         this.combinator = combinator;
         this.criteria = criteria;
+        this.limit = Optional.ofNullable(limit).orElse(Optional.empty());
     }
 
     public String getCombinator() {
@@ -55,19 +65,24 @@ public class QueryDTO implements QueryElement {
         return criteria;
     }
 
+    public Optional<Long> getLimit() {
+        return limit;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof QueryDTO) {
             QueryDTO queryDTO = (QueryDTO) o;
 
             return Objects.equals(this.combinator, queryDTO.getCombinator())
-                && Objects.equals(this.criteria, queryDTO.getCriteria());
+                && Objects.equals(this.criteria, queryDTO.getCriteria())
+                && Objects.equals(this.limit, queryDTO.getLimit());
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(combinator, criteria);
+        return Objects.hash(combinator, criteria, limit);
     }
 }
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryTranslator.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryTranslator.java
index 6619c8c845..2ed94abc74 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryTranslator.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/dto/query/QueryTranslator.java
@@ -198,7 +198,7 @@ public class QueryTranslator {
         List<QueryElement> queryElements = query.getCriteria().stream()
             .map(this::toDTO)
             .collect(ImmutableList.toImmutableList());
-        return new QueryDTO(Combinator.AND.getValue(), queryElements);
+        return new QueryDTO(Combinator.AND.getValue(), queryElements, query.getLimit());
     }
 
     private CriterionDTO toDTO(Criterion<?> criterion) {
@@ -224,7 +224,7 @@ public class QueryTranslator {
         return Query.and(queryDTO.getCriteria().stream()
             .map(queryElement -> (CriterionDTO) queryElement)
             .map(Throwing.function(this::translate))
-            .collect(ImmutableList.toImmutableList()));
+            .collect(ImmutableList.toImmutableList()), queryDTO.getLimit());
     }
 
     private boolean combinatorIsValid(String combinator) {
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
index f73e067ecd..779f3ac9f8 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
@@ -20,10 +20,12 @@
 package org.apache.james.vault.search;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Predicate;
 
 import org.apache.james.vault.DeletedMessage;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 public class Query {
@@ -34,14 +36,31 @@ public class Query {
         return new Query(criteria);
     }
 
+    public static Query and(List<Criterion<?>> criteria, Optional<Long> limit) {
+        return new Query(criteria, limit.orElse(null));
+    }
+
     public static Query of(Criterion<?>... criteria) {
         return new Query(ImmutableList.copyOf(criteria));
     }
 
+    public static Query of(long limit, List<Criterion<?>> criteria) {
+        return new Query(criteria, limit);
+    }
+
     private final List<Criterion<?>> criteria;
 
+    private final Optional<Long> limit;
+
     private Query(List<Criterion<?>> criteria) {
         this.criteria = criteria;
+        this.limit = Optional.empty();
+    }
+
+    public Query(List<Criterion<?>> criteria, Long limit) {
+        Preconditions.checkArgument(limit == null || limit > 0, "Limit should be strictly positive");
+        this.criteria = criteria;
+        this.limit = Optional.ofNullable(limit);
     }
 
     public Predicate<DeletedMessage> toPredicate() {
@@ -54,4 +73,8 @@ public class Query {
     public List<Criterion<?>> getCriteria() {
         return criteria;
     }
+
+    public Optional<Long> getLimit() {
+        return limit;
+    }
 }
diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultContract.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultContract.java
index 7d3e9d953e..cf82d47a4a 100644
--- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultContract.java
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultContract.java
@@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.io.ByteArrayInputStream;
 import java.time.Clock;
 import java.time.Duration;
+import java.util.List;
 
 import org.apache.james.mailbox.inmemory.InMemoryMessageId;
 import org.apache.james.task.Task;
@@ -127,6 +128,21 @@ public interface DeletedMessageVaultContract {
             .containsOnly(DELETED_MESSAGE, DELETED_MESSAGE_2);
     }
 
+    @Test
+    default void searchAllShouldSupportLimitQuery() {
+        Mono.from(getVault().append(DELETED_MESSAGE, new ByteArrayInputStream(CONTENT))).block();
+        Mono.from(getVault().append(DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT))).block();
+        DeletedMessage deletedMessage3 = DELETED_MESSAGE_GENERATOR.apply(InMemoryMessageId.of(33).getRawId());
+        Mono.from(getVault().append(deletedMessage3, new ByteArrayInputStream(CONTENT))).block();
+
+        assertThat(Flux.from(getVault().search(USERNAME, Query.of(1, List.of()))).collectList().block())
+            .hasSize(1);
+        assertThat(Flux.from(getVault().search(USERNAME, Query.of(3, List.of()))).collectList().block())
+            .containsExactlyInAnyOrder(DELETED_MESSAGE, DELETED_MESSAGE_2, deletedMessage3);
+        assertThat(Flux.from(getVault().search(USERNAME, Query.of(4, List.of()))).collectList().block())
+            .containsExactlyInAnyOrder(DELETED_MESSAGE, DELETED_MESSAGE_2, deletedMessage3);
+    }
+
     @Test
     default void searchShouldReturnMatchingItems() {
         Mono.from(getVault().append(DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT))).block();
diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryElementSerializerTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryElementSerializerTest.java
index cc983fd7b1..77ba5f63e9 100644
--- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryElementSerializerTest.java
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryElementSerializerTest.java
@@ -22,6 +22,7 @@ package org.apache.james.vault.dto.query;
 import static org.apache.james.vault.DeletedMessageFixture.SUBJECT;
 import static org.apache.mailet.base.MailAddressFixture.SENDER;
 
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 import org.apache.james.vault.search.FieldName;
 import org.apache.james.vault.search.Operator;
 import org.junit.jupiter.api.BeforeEach;
@@ -38,6 +39,7 @@ class QueryElementSerializerTest {
     @BeforeEach
     void beforeEach() {
         ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.registerModule(new Jdk8Module());
         queryElementSerializer = new QueryElementSerializer(objectMapper);
     }
 
@@ -54,9 +56,11 @@ class QueryElementSerializerTest {
         JsonAssertions.assertThatJson(queryElementSerializer.serialize(queryDTO))
             .isEqualTo("{  " +
                 "  \"combinator\": \"and\",  " +
+                "  \"limit\": null,  " +
                 "  \"criteria\": [  " +
                 "    {  " +
                 "      \"combinator\": \"and\",  " +
+                "      \"limit\": null,  " +
                 "      \"criteria\": [  " +
                 "        {\"fieldName\": \"subject\", \"operator\": \"contains\", \"value\": \"" + SUBJECT + "\"}," +
                 "        {\"fieldName\": \"sender\", \"operator\": \"equals\", \"value\": \"" + SENDER.asString() + "\"}" +
@@ -70,7 +74,7 @@ class QueryElementSerializerTest {
     @Test
     void shouldSerializeFlattenStructure() throws Exception {
 
-        QueryDTO queryDTO = QueryDTO.and(
+        QueryDTO queryDTO = QueryDTO.and(1L,
             CriterionDTO.from(FieldName.SUBJECT, Operator.CONTAINS, SUBJECT),
             CriterionDTO.from(FieldName.SENDER, Operator.EQUALS, SENDER.asString()),
             CriterionDTO.from(FieldName.HAS_ATTACHMENT, Operator.EQUALS, "true")
@@ -79,6 +83,7 @@ class QueryElementSerializerTest {
         JsonAssertions.assertThatJson(queryElementSerializer.serialize(queryDTO))
             .isEqualTo("{  " +
                 "  \"combinator\": \"and\",  " +
+                "  \"limit\": 1,  " +
                 "  \"criteria\": [  " +
                 "    {\"fieldName\": \"subject\", \"operator\": \"contains\", \"value\": \"" + SUBJECT + "\"}," +
                 "    {\"fieldName\": \"sender\", \"operator\": \"equals\", \"value\": \"" + SENDER.asString() + "\"}," +
diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryTranslatorTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryTranslatorTest.java
index 301d720c28..6450b33a82 100644
--- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryTranslatorTest.java
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/dto/query/QueryTranslatorTest.java
@@ -24,6 +24,8 @@ import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Optional;
 
 import org.apache.james.core.MailAddress;
 import org.apache.james.mailbox.inmemory.InMemoryId;
@@ -48,7 +50,7 @@ class QueryTranslatorTest {
 
     @Test
     void translateShouldThrowWhenPassingNotAndOperator() {
-        assertThatThrownBy(() -> queryTranslator.translate(new QueryDTO("or", ImmutableList.of())))
+        assertThatThrownBy(() -> queryTranslator.translate(new QueryDTO("or", ImmutableList.of(), Optional.empty())))
             .isInstanceOf(IllegalArgumentException.class)
             .hasMessage("combinator 'or' is not yet handled");
     }
@@ -56,7 +58,7 @@ class QueryTranslatorTest {
     @Test
     void translateShouldNotThrowWhenPassingNullOperator() {
         String nullOperator = null;
-        assertThatCode(() -> queryTranslator.translate(new QueryDTO(nullOperator, ImmutableList.of())))
+        assertThatCode(() -> queryTranslator.translate(new QueryDTO(nullOperator, ImmutableList.of(), Optional.empty())))
             .doesNotThrowAnyException();
     }
 
@@ -216,4 +218,17 @@ class QueryTranslatorTest {
             new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "true")
         ));
     }
+
+    @Test
+    void toDTOShouldSuccessWhenHasLimitQuery() throws Exception {
+        Query query = Query.of(11,
+            List.of(CriterionFactory.subject().contains("james"),
+                CriterionFactory.hasSender(new MailAddress("user@james.org")),
+                CriterionFactory.hasAttachment(true)));
+
+        assertThat(queryTranslator.toDTO(query)).isEqualTo(QueryDTO.and(11L,
+            new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS.getValue(), "james"),
+            new CriterionDTO(FieldName.SENDER.getValue(), Operator.EQUALS.getValue(), "user@james.org"),
+            new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "true")));
+    }
 }
\ No newline at end of file
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
index fa1a311569..72a4d6bb0f 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
@@ -4610,6 +4610,16 @@ criterion list to represent `matching all deleted messages`:
 }
 ....
 
+* For limiting the number of restored messages, you can use the `limit` query property:
+
+....
+{
+  "combinator": "and",
+  "limit": 99
+  "criteria": []
+}
+....
+
 *Warning*: Current web-admin uses `US` locale as the default. Therefore,
 there might be some conflicts when using String `containsIgnoreCase`
 comparators to apply on the String data of other special locales stored
diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
index 466fb8e05b..89695a57d3 100644
--- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
@@ -350,6 +350,32 @@ public abstract class DeletedMessageVaultIntegrationTest {
             .isEqualTo(0);
     }
 
+    @Test
+    void postShouldRestoreMatchingMessagesWhenQueryLimit() {
+        bartSendMessageToHomerWithSubject("aaaa");
+        bartSendMessageToHomerWithSubject("aaaa");
+        WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2));
+
+        homerDeletesMessages(listMessageIdsForAccount(homerAccessToken));
+
+        WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0));
+
+        String query = "{" +
+            "  \"combinator\": \"and\"," +
+            "  \"limit\": 1," +
+            "  \"criteria\": [" +
+            "    {" +
+            "      \"fieldName\": \"subject\"," +
+            "      \"operator\": \"equals\"," +
+            "      \"value\": \"aaaa\"" +
+            "    }" +
+            "  ]" +
+            "}";
+        restoreMessagesForUserWithQuery(webAdminApi, HOMER, query);
+
+        WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1));
+    }
+
     @Test
     void imapMovedMessageShouldNotEndUpInTheVault(GuiceJamesServer jmapServer) throws Exception {
         bartSendMessageToHomer();
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
index 88032e17eb..39571fd587 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
@@ -25,6 +25,7 @@ import static io.restassured.RestAssured.with;
 import static org.apache.james.vault.DeletedMessageFixture.CONTENT;
 import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE;
 import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE_2;
+import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE_GENERATOR;
 import static org.apache.james.vault.DeletedMessageFixture.DELETION_DATE;
 import static org.apache.james.vault.DeletedMessageFixture.DELIVERY_DATE;
 import static org.apache.james.vault.DeletedMessageFixture.FINAL_STAGE;
@@ -1793,6 +1794,49 @@ class DeletedMessagesVaultRoutesTest {
                 .isFalse();
         }
 
+        @Test
+        void restoreShouldSupportLimitQuery() throws Exception {
+            Mono.from(vault.append(FINAL_STAGE.get()
+                .subject("subject contains should match")
+                .build(), new ByteArrayInputStream(CONTENT))).block();
+
+            DeletedMessage deletedMessage2 = DELETED_MESSAGE_GENERATOR.apply(InMemoryMessageId.of(22).getRawId());
+
+            Mono.from(vault.append(deletedMessage2, new ByteArrayInputStream(CONTENT))).block();
+
+            String query = "{" +
+                "  \"combinator\": \"and\"," +
+                "  \"limit\": 1," +
+                "  \"criteria\": [" +
+                "    {" +
+                "      \"fieldName\": \"sender\"," +
+                "      \"operator\": \"equals\"," +
+                "      \"value\": \"" + SENDER.asString() + "\"" +
+                "    }" +
+                "  ]" +
+                "}";
+
+            String taskId =
+                given()
+                    .queryParam("action", "restore")
+                    .body(query)
+                .when()
+                    .post(BOB_PATH)
+                    .jsonPath()
+                    .get("taskId");
+
+            given()
+                .basePath(TasksRoutes.BASE)
+            .when()
+                .get(taskId + "/await")
+            .then()
+                .body("status", is("completed"))
+                .body("additionalInformation.successfulRestoreCount", is(1));
+
+            assertThat(restoreMessageContents(USERNAME))
+                .hasSize(1);
+        }
+
     }
 
     @Nested
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/WebadminApiQuerySerializationContractTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/WebadminApiQuerySerializationContractTest.java
index 7446c165b7..aadc8d736d 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/WebadminApiQuerySerializationContractTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/WebadminApiQuerySerializationContractTest.java
@@ -19,6 +19,7 @@
 package org.apache.james.webadmin.vault.routes;
 
 import java.time.ZonedDateTime;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.james.mailbox.model.MailboxId;
@@ -48,47 +49,47 @@ class WebadminApiQuerySerializationContractTest {
 
     private static final String HAS_ATTACHMENT_FILE = "has_attachment.json";
     private static final QueryDTO HAS_ATTACHMENT_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "true")));
+        ImmutableList.of(new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "true")), Optional.empty());
 
     private static final String HAS_NO_ATTACHMENT_FILE = "has_no_attachment.json";
     private static final QueryDTO HAS_NO_ATTACHMENT_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "false")));
+        ImmutableList.of(new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), Operator.EQUALS.getValue(), "false")), Optional.empty());
 
     private static final String HAS_SENDER_FILE = "has_sender.json";
     private static final QueryDTO HAS_SENDER_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.SENDER.getValue(), Operator.EQUALS.getValue(), USER_JAMES)));
+        ImmutableList.of(new CriterionDTO(FieldName.SENDER.getValue(), Operator.EQUALS.getValue(), USER_JAMES)), Optional.empty());
 
     private static final String CONTAINS_RECIPIENT_FILE = "contains_recipient.json";
     private static final QueryDTO CONTAINS_RECIPIENT_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.RECIPIENTS.getValue(), Operator.CONTAINS.getValue(), USER_JAMES)));
+        ImmutableList.of(new CriterionDTO(FieldName.RECIPIENTS.getValue(), Operator.CONTAINS.getValue(), USER_JAMES)), Optional.empty());
 
     private static final String CONTAINS_ORIGIN_MAILBOX_FILE = "contains_origin_mailbox.json";
     private static final QueryDTO CONTAINS_ORIGIN_MAILBOX_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.ORIGIN_MAILBOXES.getValue(), Operator.CONTAINS.getValue(), MAILBOX_1_ID.serialize())));
+        ImmutableList.of(new CriterionDTO(FieldName.ORIGIN_MAILBOXES.getValue(), Operator.CONTAINS.getValue(), MAILBOX_1_ID.serialize())), Optional.empty());
 
     private static final String DELIVERY_BEFORE_FILE = "zoned_date_time_before_or_equals.json";
     private static final QueryDTO DELIVERY_BEFORE_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.DELIVERY_DATE.getValue(), Operator.BEFORE_OR_EQUALS.getValue(), ZONED_DATE_TIME.toString())));
+        ImmutableList.of(new CriterionDTO(FieldName.DELIVERY_DATE.getValue(), Operator.BEFORE_OR_EQUALS.getValue(), ZONED_DATE_TIME.toString())), Optional.empty());
 
     private static final String DELETED_AFTER_FILE = "zoned_date_time_after_or_equals.json";
     private static final QueryDTO DELETED_AFTER_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.DELETION_DATE.getValue(), Operator.AFTER_OR_EQUALS.getValue(), ZONED_DATE_TIME.toString())));
+        ImmutableList.of(new CriterionDTO(FieldName.DELETION_DATE.getValue(), Operator.AFTER_OR_EQUALS.getValue(), ZONED_DATE_TIME.toString())), Optional.empty());
 
     private static final String SUBJECT_CONTAINS_FILE = "string_contains.json";
     private static final QueryDTO SUBJECT_CONTAINS_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS.getValue(), SUBJECT)));
+        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS.getValue(), SUBJECT)), Optional.empty());
 
     private static final String SUBJECT_CONTAINS_IGNORE_CASE_FILE = "string_contains_ignore_case.json";
     private static final QueryDTO SUBJECT_CONTAINS_IGNORE_CASE_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS_IGNORE_CASE.getValue(), SUBJECT)));
+        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS_IGNORE_CASE.getValue(), SUBJECT)), Optional.empty());
 
     private static final String SUBJECT_EQUALS_FILE = "string_equals.json";
     private static final QueryDTO SUBJECT_EQUALS_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.EQUALS.getValue(), SUBJECT)));
+        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.EQUALS.getValue(), SUBJECT)), Optional.empty());
 
     private static final String SUBJECT_EQUALS_IGNORE_CASE_FILE = "string_equals_ignore_case.json";
     private static final QueryDTO SUBJECT_EQUALS_IGNORE_CASE_DTO = new QueryDTO(AND,
-        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.EQUALS_IGNORE_CASE.getValue(), SUBJECT)));
+        ImmutableList.of(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.EQUALS_IGNORE_CASE.getValue(), SUBJECT)), Optional.empty());
 
     private static final TestId.Factory mailboxIdFactory = new TestId.Factory();
     private static final QueryTranslator queryTranslator = new QueryTranslator(mailboxIdFactory);
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 5ea35c2dd3..8c78ca3518 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -4267,6 +4267,15 @@ Messages in the Deleted Messages Vault of a specified user that are matched with
   "criteria": []
 }
 ```
+- For limiting the number of restored messages, you can use the `limit` query property:
+
+```
+{
+  "combinator": "and",
+  "limit": 99
+  "criteria": []
+}
+```
 
 **Warning**: Current web-admin uses `US` locale as the default. Therefore, there might be some conflicts when using String `containsIgnoreCase` comparators to apply 
 on the String data of other special locales stored in the Vault. More details at [JIRA](https://issues.apache.org/jira/browse/MAILBOX-384) 


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org