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/03/16 15:23:51 UTC

[james-project] 01/02: JAMES-3440 Fixbug: Data race issue with JMAP email query view

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 29a253f596aa3693e2f9b0b46fe51c3a68bc178c
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Sat Mar 11 09:50:56 2023 +0700

    JAMES-3440 Fixbug: Data race issue with JMAP email query view
---
 .../jmap/event/PopulateEmailQueryViewListener.java | 17 ++++++-
 .../event/PopulateEmailQueryViewListenerTest.java  | 54 +++++++++++++++++++++-
 2 files changed, 68 insertions(+), 3 deletions(-)

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 9df59c69f4..de07c18370 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
@@ -36,6 +36,7 @@ import org.apache.james.events.Group;
 import org.apache.james.jmap.api.projections.EmailQueryView;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.Role;
 import org.apache.james.mailbox.SessionProvider;
 import org.apache.james.mailbox.events.MailboxEvents.Added;
 import org.apache.james.mailbox.events.MailboxEvents.Expunged;
@@ -54,6 +55,7 @@ import org.apache.james.mime4j.dom.field.DateTimeField;
 import org.apache.james.mime4j.field.DateTimeFieldLenientImpl;
 import org.apache.james.mime4j.message.DefaultMessageBuilder;
 import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.util.FunctionalUtils;
 import org.reactivestreams.Publisher;
 
 import com.google.common.collect.ImmutableList;
@@ -156,11 +158,24 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene
 
     private Mono<Void> handleAdded(Added added, MessageMetaData messageMetaData, MailboxSession session) {
         MessageId messageId = messageMetaData.getMessageId();
+        MailboxId mailboxId = added.getMailboxId();
 
-        return Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.HEADERS, session))
+        Mono<Void> doHandleAdded = Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.HEADERS, session))
             .next()
             .filter(message -> !message.getFlags().contains(DELETED))
             .flatMap(messageResult -> handleAdded(added.getMailboxId(), messageResult));
+        if (Role.from(added.getMailboxPath().getName()).equals(Optional.of(Role.OUTBOX))) {
+            return checkMessageStillInOriginMailbox(messageId, session, mailboxId)
+                .filter(FunctionalUtils.identityPredicate())
+                .flatMap(stillInOriginMailbox -> doHandleAdded);
+        }
+        return doHandleAdded;
+    }
+
+    private Mono<Boolean> checkMessageStillInOriginMailbox(MessageId messageId, MailboxSession session, MailboxId targetMailboxId) {
+        return Flux.from(messageIdManager.messageMetadata(messageId, session))
+            .filter(composedMessageIdWithMetaData -> composedMessageIdWithMetaData.getComposedMessageId().getMailboxId().equals(targetMailboxId))
+            .hasElements();
     }
 
     public Mono<Void> handleAdded(MailboxId mailboxId, MessageResult messageResult) {
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 2f62f4414a..66de6a59ad 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
@@ -20,16 +20,19 @@
 package org.apache.james.jmap.event;
 
 import static javax.mail.Flags.Flag.DELETED;
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.ZonedDateTime;
 import java.util.Date;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
 import org.apache.james.core.Username;
+import org.apache.james.events.Event;
 import org.apache.james.events.Group;
 import org.apache.james.events.InVMEventBus;
 import org.apache.james.events.MemoryEventDeadLetters;
@@ -40,11 +43,16 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MailboxSessionUtil;
 import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.events.MailboxEvents;
 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.MessageMetaData;
 import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.store.FakeAuthenticator;
 import org.apache.james.mailbox.store.FakeAuthorizator;
 import org.apache.james.mailbox.store.SessionProviderImpl;
@@ -56,6 +64,9 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+
+import reactor.core.publisher.Mono;
 
 public class PopulateEmailQueryViewListenerTest {
     private static final Username BOB = Username.of("bob");
@@ -69,6 +80,7 @@ public class PopulateEmailQueryViewListenerTest {
     MessageManager otherBoxMessageManager;
     PopulateEmailQueryViewListener listener;
     MessageIdManager messageIdManager;
+    SessionProviderImpl sessionProvider;
     private MemoryEmailQueryView view;
     private MailboxId inboxId;
 
@@ -97,7 +109,7 @@ public class PopulateEmailQueryViewListenerTest {
 
         FakeAuthenticator authenticator = new FakeAuthenticator();
         authenticator.addUser(BOB, "12345");
-        SessionProviderImpl sessionProvider = new SessionProviderImpl(authenticator, FakeAuthorizator.defaultReject());
+        sessionProvider = new SessionProviderImpl(authenticator, FakeAuthorizator.defaultReject());
 
         view = new MemoryEmailQueryView();
         listener = new PopulateEmailQueryViewListener(messageIdManager, view, sessionProvider);
@@ -133,7 +145,7 @@ public class PopulateEmailQueryViewListenerTest {
     }
 
     @Test
-    void appendingADeletedMessageSHouldNotAddItToTheView() throws Exception {
+    void appendingADeletedMessageShouldNotAddItToTheView() throws Exception {
         inboxMessageManager.appendMessage(
             MessageManager.AppendCommand.builder()
                 .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
@@ -145,6 +157,44 @@ public class PopulateEmailQueryViewListenerTest {
             .isEmpty();
     }
 
+    @Test
+    void appendingAOutdatedMessageInOutBoxShouldNotAddItToTheView() throws Exception {
+        MemoryEmailQueryView emailQueryView = new MemoryEmailQueryView();
+        PopulateEmailQueryViewListener queryViewListener = new PopulateEmailQueryViewListener(messageIdManager, emailQueryView, sessionProvider);
+        MailboxPath outboxPath = MailboxPath.forUser(BOB, "Outbox");
+        MailboxId outboxId = mailboxManager.createMailbox(outboxPath, mailboxSession).orElseThrow();
+
+        // given: save a message in Inbox
+        ComposedMessageId composedId = 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();
+
+        // mock an outDated message by assigning above message in OutBox
+        MessageMetaData outdatedMessageMetaData = new MessageMetaData(MessageUid.of(1),
+            ModSeq.of(35), new Flags(), 12,
+            new Date(),
+            Optional.empty(),
+            composedId.getMessageId(),
+            ThreadId.fromBaseMessageId(composedId.getMessageId()));
+
+        // the latest mailboxId should be `composedId.getMailboxId()`, not `outboxId`
+        MailboxEvents.Added addedOutDatedEvent = new MailboxEvents.Added(MailboxSession.SessionId.of(42),
+            BOB,
+            outboxPath,
+            outboxId,
+            ImmutableSortedMap.of(MessageUid.of(1), outdatedMessageMetaData),
+            Event.EventId.random(),
+            !IS_DELIVERY);
+
+        Mono.from(queryViewListener.reactiveEvent(addedOutDatedEvent)).block();
+
+        assertThat(emailQueryView.listMailboxContentSortedBySentAt(outboxId, Limit.limit(12)).collectList().block())
+            .isEmpty();
+    }
+
+
     @Test
     void removingDeletedFlagsShouldAddItToTheView() throws Exception {
         ComposedMessageId composedId = inboxMessageManager.appendMessage(


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