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 2021/06/14 02:04:39 UTC

[james-project] 02/02: JAMES-3597 Exclude deleted messages from JMAP Email/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 bd6277c44bdbe905c7b28dabe37a5a57959e572a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jun 11 13:46:55 2021 +0700

    JAMES-3597 Exclude deleted messages from JMAP Email/query
---
 .../mailbox/model/MultimailboxesSearchQuery.java   | 12 +++++
 .../integration/GetMessageListMethodTest.java      | 48 --------------------
 .../jmap/draft/methods/GetMessageListMethod.java   |  4 +-
 .../jmap/event/PopulateEmailQueryViewListener.java | 51 +++++++++++++++++++---
 .../event/PopulateEmailQueryViewListenerTest.java  | 46 +++++++++++++++++++
 .../contract/EmailQueryMethodContract.scala        |  5 +--
 .../james/jmap/method/EmailQueryMethod.scala       |  8 +++-
 .../data/jmap/EmailQueryViewPopulator.java         |  4 +-
 .../PopulateEmailQueryViewRequestToTaskTest.java   | 25 +++++++++++
 9 files changed, 143 insertions(+), 60 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
index 4785111..7db38f8 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
@@ -171,4 +171,16 @@ public class MultimailboxesSearchQuery {
     public Namespace getNamespace() {
         return namespace;
     }
+
+    public MultimailboxesSearchQuery addCriterion(SearchQuery.Criterion criterion) {
+        return new MultimailboxesSearchQuery(
+            SearchQuery.builder()
+                .andCriteria(searchQuery.getCriteria())
+                .andCriteria(criterion)
+                .sorts(searchQuery.getSorts())
+                .build(),
+            inMailboxes,
+            notInMailboxes,
+            namespace);
+    }
 }
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java
index 44ca726..bcdb080 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java
@@ -94,7 +94,6 @@ import org.apache.james.utils.TestIMAPClient;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
@@ -222,7 +221,6 @@ public abstract class GetMessageListMethodTest {
             .body(ARGUMENTS + ".messageIds", contains(messageId));
     }
 
-    @Ignore("JAMES-3597 Deleted messages are exposed over JMAP")
     @Test
     public void getMessageListShouldFilterMessagesMarkedAsDeleted() throws Exception {
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox");
@@ -2187,29 +2185,6 @@ public abstract class GetMessageListMethodTest {
     }
 
     @Test
-    public void getMessageListHasKeywordShouldIgnoreDeleted() throws Exception {
-        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox");
-
-        ComposedMessageId messageNotFlagged = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX,
-            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
-        ComposedMessageId messageFlagged = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX,
-            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags(Flags.Flag.DELETED));
-
-        await();
-
-        given()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"hasKeyword\":\"$Deleted\"}}, \"#0\"]]")
-        .when()
-            .post("/jmap")
-        .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds",
-                containsInAnyOrder(messageFlagged.getMessageId().serialize(), messageNotFlagged.getMessageId().serialize()));
-    }
-
-    @Test
     public void getMessageListHasKeywordShouldIgnoreRecent() throws Exception {
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox");
 
@@ -2233,29 +2208,6 @@ public abstract class GetMessageListMethodTest {
     }
 
     @Test
-    public void getMessageListNotKeywordShouldIgnoreDeleted() throws Exception {
-        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox");
-
-        ComposedMessageId messageNotFlagged = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX,
-            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
-        ComposedMessageId messageFlagged = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX,
-            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags(Flags.Flag.DELETED));
-
-        await();
-
-        given()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"notKeyword\":\"$Deleted\"}}, \"#0\"]]")
-        .when()
-            .post("/jmap")
-        .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds",
-                containsInAnyOrder(messageFlagged.getMessageId().serialize(), messageNotFlagged.getMessageId().serialize()));
-    }
-
-    @Test
     public void getMessageListNotKeywordShouldIgnoreRecent() throws Exception {
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox");
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java
index bbf09d4..e655108 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.draft.methods;
 
+import static javax.mail.Flags.Flag.DELETED;
 import static org.apache.james.util.ReactorUtils.context;
 
 import java.time.ZonedDateTime;
@@ -221,7 +222,8 @@ public class GetMessageListMethod implements Method {
             .subscribeOn(Schedulers.parallel());
 
         return searchQuery
-            .flatMapMany(Throwing.function(query -> mailboxManager.search(query, mailboxSession, limit)))
+            .flatMapMany(Throwing.function(query ->
+                mailboxManager.search(query.addCriterion(SearchQuery.flagIsUnSet(DELETED)), mailboxSession, limit)))
             .skip(position)
             .reduce(GetMessageListResponse.builder(), GetMessageListResponse.Builder::messageId)
             .map(GetMessageListResponse.Builder::build);
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
index 8dbf751..722cd52 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
@@ -19,6 +19,9 @@
 
 package org.apache.james.jmap.event;
 
+import static javax.mail.Flags.Flag.DELETED;
+import static org.apache.james.util.ReactorUtils.publishIfPresent;
+
 import java.io.IOException;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
@@ -35,17 +38,19 @@ import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.SessionProvider;
 import org.apache.james.mailbox.events.MailboxEvents.Added;
 import org.apache.james.mailbox.events.MailboxEvents.Expunged;
+import org.apache.james.mailbox.events.MailboxEvents.FlagsUpdated;
 import org.apache.james.mailbox.events.MailboxEvents.MailboxDeletion;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageMetaData;
 import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.mailbox.model.UpdatedFlags;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.stream.MimeConfig;
 import org.reactivestreams.Publisher;
 
-import com.github.fge.lambdas.Throwing;
 import com.google.common.collect.ImmutableList;
 
 import reactor.core.publisher.Flux;
@@ -78,6 +83,7 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene
     @Override
     public boolean isHandling(Event event) {
         return event instanceof Added
+            || event instanceof FlagsUpdated
             || event instanceof Expunged
             || event instanceof MailboxDeletion;
     }
@@ -90,6 +96,9 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene
         if (event instanceof Expunged) {
             return handleExpunged((Expunged) event);
         }
+        if (event instanceof FlagsUpdated) {
+            return handleFlagsUpdated((FlagsUpdated) event);
+        }
         if (event instanceof MailboxDeletion) {
             return handleMailboxDeletion((MailboxDeletion) event);
         }
@@ -107,6 +116,31 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene
             .then();
     }
 
+
+    private Publisher<Void> handleFlagsUpdated(FlagsUpdated flagsUpdated) {
+        MailboxSession session = sessionProvider.createSystemSession(flagsUpdated.getUsername());
+
+        Mono<Void> removeMessagesMarkedAsDeleted = Flux.fromIterable(flagsUpdated.getUpdatedFlags())
+            .filter(updatedFlags -> updatedFlags.isModifiedToSet(DELETED))
+            .map(UpdatedFlags::getMessageId)
+            .handle(publishIfPresent())
+            .concatMap(messageId -> view.delete(flagsUpdated.getMailboxId(), messageId))
+            .then();
+
+        Mono<Void> addMessagesNoLongerMarkedAsDeleted = Flux.fromIterable(flagsUpdated.getUpdatedFlags())
+            .filter(updatedFlags -> updatedFlags.isModifiedToUnset(DELETED))
+            .map(UpdatedFlags::getMessageId)
+            .handle(publishIfPresent())
+            .concatMap(messageId ->
+                Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.HEADERS, session))
+                    .next())
+            .concatMap(message -> handleAdded(flagsUpdated.getMailboxId(), message))
+            .then();
+
+        return removeMessagesMarkedAsDeleted
+            .then(addMessagesNoLongerMarkedAsDeleted);
+    }
+
     private Mono<Void> handleAdded(Added added) {
         MailboxSession session = sessionProvider.createSystemSession(added.getUsername());
         return Flux.fromStream(added.getUids().stream()
@@ -117,14 +151,21 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene
 
     private Mono<Void> handleAdded(Added added, MessageMetaData messageMetaData, MailboxSession session) {
         MessageId messageId = messageMetaData.getMessageId();
-        ZonedDateTime receivedAt = ZonedDateTime.ofInstant(messageMetaData.getInternalDate().toInstant(), ZoneOffset.UTC);
 
         return Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.HEADERS, session))
             .next()
-            .map(Throwing.function(this::parseMessage))
-            .map(message -> Optional.ofNullable(message.getDate()).orElse(messageMetaData.getInternalDate()))
+            .filter(message -> !message.getFlags().contains(DELETED))
+            .flatMap(messageResult -> handleAdded(added.getMailboxId(), messageResult));
+    }
+
+    public Mono<Void> handleAdded(MailboxId mailboxId, MessageResult messageResult) {
+        ZonedDateTime receivedAt = ZonedDateTime.ofInstant(messageResult.getInternalDate().toInstant(), ZoneOffset.UTC);
+
+        return Mono.fromCallable(() -> parseMessage(messageResult))
+            .map(message -> Optional.ofNullable(message.getDate()).orElse(messageResult.getInternalDate()))
             .map(date -> ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC))
-            .flatMap(sentAt -> view.save(added.getMailboxId(), sentAt, receivedAt, messageId));
+            .flatMap(sentAt -> view.save(mailboxId, sentAt, receivedAt, messageResult.getMessageId()))
+            .then();
     }
 
     private Message parseMessage(MessageResult messageResult) throws IOException, MailboxException {
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java
index e77ace4..98a75c3 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.event;
 
+import static javax.mail.Flags.Flag.DELETED;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.nio.charset.StandardCharsets;
@@ -26,6 +27,8 @@ import java.time.Duration;
 import java.time.ZonedDateTime;
 import java.util.Date;
 
+import javax.mail.Flags;
+
 import org.apache.james.core.Username;
 import org.apache.james.events.Group;
 import org.apache.james.events.InVMEventBus;
@@ -41,6 +44,7 @@ import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.store.FakeAuthenticator;
 import org.apache.james.mailbox.store.FakeAuthorizator;
 import org.apache.james.mailbox.store.SessionProviderImpl;
@@ -129,6 +133,48 @@ public class PopulateEmailQueryViewListenerTest {
     }
 
     @Test
+    void appendingADeletedMessageSHouldNotAddItToTheView() throws Exception {
+        inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+                .withFlags(new Flags(DELETED))
+                .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+            mailboxSession).getId();
+
+        assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+            .isEmpty();
+    }
+
+    @Test
+    void removingDeletedFlagsShouldAddItToTheView() throws Exception {
+        ComposedMessageId composedId = inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+                .withFlags(new Flags(DELETED))
+                .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+            mailboxSession).getId();
+
+        inboxMessageManager.setFlags(new Flags(), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.all(), mailboxSession);
+
+        assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+            .containsOnly(composedId.getMessageId());
+    }
+
+    @Test
+    void addingDeletedFlagsShouldRemoveItToTheView() throws Exception {
+        inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+                .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+            mailboxSession).getId();
+
+        inboxMessageManager.setFlags(new Flags(DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.all(), mailboxSession);
+
+        assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+            .isEmpty();
+    }
+
+    @Test
     void deletingMailboxShouldClearTheView() throws Exception {
         inboxMessageManager.appendMessage(
             MessageManager.AppendCommand.builder()
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index f2b1cbd..ca93414 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -55,7 +55,7 @@ import org.apache.james.util.ClassLoaderUtils
 import org.apache.james.utils.DataProbeImpl
 import org.awaitility.Awaitility
 import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
-import org.junit.jupiter.api.{BeforeEach, Disabled, Test}
+import org.junit.jupiter.api.{BeforeEach, Test}
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.{Arguments, MethodSource, ValueSource}
 import org.threeten.extra.Seconds
@@ -206,9 +206,8 @@ trait EmailQueryMethodContract {
     }
   }
 
-  @Disabled("JAMES-3597 Deleted messages are exposed over JMAP")
   @Test
-  def messagesMarkedAsDeeltedShouldNotBeExposedOverJMAP(server: GuiceJamesServer): Unit = {
+  def messagesMarkedAsDeletedShouldNotBeExposedOverJMAP(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index 186e80c..ad4cace 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -23,6 +23,7 @@ import java.time.ZonedDateTime
 import cats.implicits._
 import eu.timepit.refined.auto._
 import javax.inject.Inject
+import javax.mail.Flags.Flag.DELETED
 import org.apache.james.jmap.JMAPConfiguration
 import org.apache.james.jmap.api.projections.EmailQueryView
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
@@ -36,7 +37,7 @@ import org.apache.james.jmap.routes.SessionSupplier
 import org.apache.james.jmap.utils.search.MailboxFilter
 import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.mailbox.exception.MailboxNotFoundException
-import org.apache.james.mailbox.model.{MailboxId, MessageId, MultimailboxesSearchQuery}
+import org.apache.james.mailbox.model.{MailboxId, MessageId, MultimailboxesSearchQuery, SearchQuery}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession}
 import org.apache.james.metrics.api.MetricFactory
 import org.apache.james.util.streams.{Limit => JavaLimit}
@@ -151,7 +152,10 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
       limit = Some(limitToUse).filterNot(used => request.limit.map(_.value).contains(used.value)))
 
   private def executeQueryAgainstSearchIndex(mailboxSession: MailboxSession, searchQuery: MultimailboxesSearchQuery, position: Position, limitToUse: Limit): SMono[Seq[MessageId]] =
-    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, position.value + limitToUse))
+    SFlux.fromPublisher(mailboxManager.search(
+        searchQuery.addCriterion(SearchQuery.flagIsUnSet(DELETED)),
+        mailboxSession,
+        position.value + limitToUse))
       .drop(position.value)
       .collectSeq()
 
diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java
index 7a77db5..76c1e0e 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.webadmin.data.jmap;
 
+import static javax.mail.Flags.Flag.DELETED;
 import static org.apache.james.mailbox.MailboxManager.MailboxSearchFetchType.Minimal;
 
 import java.io.IOException;
@@ -134,7 +135,8 @@ public class EmailQueryViewPopulator {
             return Iterators.toFlux(usersRepository.list())
                 .map(mailboxManager::createSystemSession)
                 .doOnNext(any -> progress.incrementProcessedUserCount())
-                .flatMap(session -> listUserMailboxMessages(progress, session), USER_CONCURRENCY);
+                .flatMap(session -> listUserMailboxMessages(progress, session), USER_CONCURRENCY)
+                .filter(messageResult -> !messageResult.getFlags().contains(DELETED));
         } catch (UsersRepositoryException e) {
             return Flux.error(e);
         }
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateEmailQueryViewRequestToTaskTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateEmailQueryViewRequestToTaskTest.java
index 402ed7e..4cda472 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateEmailQueryViewRequestToTaskTest.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/PopulateEmailQueryViewRequestToTaskTest.java
@@ -22,10 +22,13 @@ package org.apache.james.webadmin.data.jmap;
 import static io.restassured.RestAssured.given;
 import static io.restassured.RestAssured.when;
 import static io.restassured.RestAssured.with;
+import static javax.mail.Flags.Flag.DELETED;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 
+import javax.mail.Flags;
+
 import org.apache.james.core.Username;
 import org.apache.james.domainlist.api.DomainList;
 import org.apache.james.jmap.memory.projections.MemoryEmailQueryView;
@@ -268,6 +271,28 @@ class PopulateEmailQueryViewRequestToTaskTest {
     }
 
     @Test
+    void populateShouldNotUpdateProjectionForDeletedMessages() throws Exception {
+        ComposedMessageId messageId = mailboxManager.getMailbox(bobInboxboxId, bobSession).appendMessage(
+            MessageManager.AppendCommand.builder()
+                .withFlags(new Flags(DELETED))
+                .build("header: value\r\n\r\nbody"),
+            bobSession).getId();
+
+        String taskId = with()
+            .queryParam("action", "populateEmailQueryView")
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        assertThat(view.listMailboxContent(messageId.getMailboxId(), Limit.from(12)).collectList().block())
+            .isEmpty();
+    }
+
+    @Test
     void populateShouldBeIdempotent() throws Exception {
         ComposedMessageId messageId = mailboxManager.getMailbox(bobInboxboxId, bobSession).appendMessage(
             MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"),

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