You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2021/01/07 04:08:15 UTC

[james-project] branch master updated (5e80fe6 -> 463516d)

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

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


    from 5e80fe6  JAMES-3471 Disable tests on JpaMessageMapperTest using messageId, as it is not supported
     new 393118b  JAMES-3431 Fix some exception variable names in MockSMTPServer
     new 444bfad  JAMES-3471 Handle EmailChange events in MailboxChangeListener
     new 663a7f7  JAMES-3471 Provide a dummy version of CassandraEmailChangeRepository
     new f91be1a  JAMES-3471 Binding for MemoryEmailChangeRepository
     new 463516d  JAMES-3457 ADR for Mailbox/changes

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../modules/mailbox/CassandraMailboxModule.java    |   3 +
 .../james/modules/mailbox/MemoryMailboxModule.java |   4 +
 .../change/CassandraEmailChangeRepository.java}    |  38 +-
 .../apache/james/jmap/api/change/EmailChange.java  | 116 ++++++
 .../mock/smtp/server/ExtendedMailFromCommand.java  |   8 +-
 .../mock/smtp/server/ExtendedRcptToCommand.java    |   8 +-
 .../james/jmap/change/MailboxChangeListener.scala  |  10 +-
 .../jmap/change/MailboxChangeListenerTest.scala    | 441 +++++++++++++--------
 ...map-push-with-mailbox-changes-implementation.md |  99 +++++
 9 files changed, 533 insertions(+), 194 deletions(-)
 copy server/{container/guice/mailbox/src/test/java/org/apache/james/modules/mailbox/ReactiveNoopMailboxListener.java => data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java} (56%)
 create mode 100644 src/adr/0045-support-jmap-push-with-mailbox-changes-implementation.md


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


[james-project] 03/05: JAMES-3471 Provide a dummy version of CassandraEmailChangeRepository

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 663a7f7d9c0807e7cc8831da48921b3c994519a5
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Jan 5 15:05:26 2021 +0700

    JAMES-3471 Provide a dummy version of CassandraEmailChangeRepository
---
 .../modules/mailbox/CassandraMailboxModule.java    |  3 ++
 .../change/CassandraEmailChangeRepository.java     | 59 ++++++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index 722a653..a8be91d 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -28,8 +28,10 @@ import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
+import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.MailboxChangeRepository;
 import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.cassandra.change.CassandraEmailChangeRepository;
 import org.apache.james.jmap.cassandra.change.CassandraMailboxChangeRepository;
 import org.apache.james.jmap.cassandra.change.CassandraStateFactory;
 import org.apache.james.jmap.change.MailboxChangeListener;
@@ -184,6 +186,7 @@ public class CassandraMailboxModule extends AbstractModule {
         bind(MailboxManager.class).to(CassandraMailboxManager.class);
         bind(StoreMailboxManager.class).to(CassandraMailboxManager.class);
         bind(MailboxChangeRepository.class).to(CassandraMailboxChangeRepository.class);
+        bind(EmailChangeRepository.class).to(CassandraEmailChangeRepository.class);
         bind(State.Factory.class).to(CassandraStateFactory.class);
         bind(MailboxId.Factory.class).to(CassandraId.Factory.class);
         bind(State.Factory.class).to(CassandraStateFactory.class);
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java
new file mode 100644
index 0000000..f364fd9
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.cassandra.change;
+
+import java.util.Optional;
+
+import org.apache.james.jmap.api.change.EmailChange;
+import org.apache.james.jmap.api.change.EmailChangeRepository;
+import org.apache.james.jmap.api.change.EmailChanges;
+import org.apache.james.jmap.api.change.Limit;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.api.model.AccountId;
+
+import reactor.core.publisher.Mono;
+
+public class CassandraEmailChangeRepository implements EmailChangeRepository {
+
+    @Override
+    public Mono<Void> save(EmailChange change) {
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<EmailChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxIdsToReturn) {
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<EmailChanges> getSinceStateWithDelegation(AccountId accountId, State state, Optional<Limit> maxIdsToReturn) {
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<State> getLatestState(AccountId accountId) {
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<State> getLatestStateWithDelegation(AccountId accountId) {
+        return Mono.empty();
+    }
+}


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


[james-project] 01/05: JAMES-3431 Fix some exception variable names in MockSMTPServer

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 393118b695557de2dcd9b933014291fa0085b915
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Jan 6 11:32:25 2021 +0700

    JAMES-3431 Fix some exception variable names in MockSMTPServer
---
 .../apache/james/mock/smtp/server/ExtendedMailFromCommand.java    | 8 ++++----
 .../org/apache/james/mock/smtp/server/ExtendedRcptToCommand.java  | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedMailFromCommand.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedMailFromCommand.java
index 5e443c2..d4a95ae 100644
--- a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedMailFromCommand.java
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedMailFromCommand.java
@@ -73,10 +73,10 @@ public class ExtendedMailFromCommand extends BaseCommand {
                     sess.setDeclaredMessageSize(size);
                     sess.setHasMailFrom(true);
                     sess.sendResponse("250 Ok");
-                } catch (DropConnectionException var9) {
-                    throw var9;
-                } catch (RejectException var10) {
-                    sess.sendResponse(var10.getErrorResponse());
+                } catch (DropConnectionException dropConnectionException) {
+                    throw dropConnectionException;
+                } catch (RejectException rejectException) {
+                    sess.sendResponse(rejectException.getErrorResponse());
                 }
             } else {
                 sess.sendResponse("553 <" + emailAddress + "> Invalid email address.");
diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedRcptToCommand.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedRcptToCommand.java
index e9108b5..d154396 100644
--- a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedRcptToCommand.java
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ExtendedRcptToCommand.java
@@ -51,10 +51,10 @@ public class ExtendedRcptToCommand extends BaseCommand {
                     messageHandler.recipient(recipientAddress, Mail.Parameter.fromArgLine(args));
                     sess.addRecipient(recipientAddress);
                     sess.sendResponse("250 Ok");
-                } catch (DropConnectionException var6) {
-                    throw var6;
-                } catch (RejectException var7) {
-                    sess.sendResponse(var7.getErrorResponse());
+                } catch (DropConnectionException dropConnectionException) {
+                    throw dropConnectionException;
+                } catch (RejectException rejectException) {
+                    sess.sendResponse(rejectException.getErrorResponse());
                 }
 
             }


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


[james-project] 02/05: JAMES-3471 Handle EmailChange events in MailboxChangeListener

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 444bfada4529a9a65f933edcf4cda462ccf4d2a2
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Jan 5 11:04:05 2021 +0700

    JAMES-3471 Handle EmailChange events in MailboxChangeListener
---
 .../apache/james/jmap/api/change/EmailChange.java  | 116 ++++++
 .../james/jmap/change/MailboxChangeListener.scala  |  10 +-
 .../jmap/change/MailboxChangeListenerTest.scala    | 441 +++++++++++++--------
 3 files changed, 397 insertions(+), 170 deletions(-)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
index 238b898..99f6a4e 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
@@ -19,14 +19,27 @@
 
 package org.apache.james.jmap.api.change;
 
+import java.time.Clock;
 import java.time.ZonedDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Stream;
 
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
 import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.MailboxListener;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageId;
 
+import com.github.steveash.guavate.Guavate;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -114,6 +127,109 @@ public class EmailChange {
         return accountId -> state -> date -> isDelegated -> new Builder(accountId, state, date, isDelegated);
     }
 
+    public static class Factory {
+        private final Clock clock;
+        private final MailboxManager mailboxManager;
+        private final State.Factory stateFactory;
+
+        @Inject
+        public Factory(Clock clock, MailboxManager mailboxManager, State.Factory stateFactory) {
+            this.clock = clock;
+            this.mailboxManager = mailboxManager;
+            this.stateFactory = stateFactory;
+        }
+
+        public List<EmailChange> fromEvent(Event event) {
+            ZonedDateTime now = ZonedDateTime.now(clock);
+
+            if (event instanceof MailboxListener.Added) {
+                MailboxListener.Added messageAdded = (MailboxListener.Added) event;
+
+                EmailChange ownerChange = EmailChange.builder()
+                    .accountId(AccountId.fromUsername(messageAdded.getUsername()))
+                    .state(stateFactory.generate())
+                    .date(now)
+                    .isDelegated(false)
+                    .created(messageAdded.getMessageIds())
+                    .build();
+
+                Stream<EmailChange> shareeChanges = getSharees(messageAdded.getMailboxPath(), messageAdded.getUsername(), mailboxManager)
+                    .map(name -> EmailChange.builder()
+                        .accountId(AccountId.fromString(name))
+                        .state(stateFactory.generate())
+                        .date(now)
+                        .isDelegated(true)
+                        .created(messageAdded.getMessageIds())
+                        .build());
+
+                return Stream.concat(Stream.of(ownerChange), shareeChanges)
+                    .collect(Guavate.toImmutableList());
+            }
+            if (event instanceof MailboxListener.FlagsUpdated) {
+                MailboxListener.FlagsUpdated messageFlagUpdated = (MailboxListener.FlagsUpdated) event;
+
+                EmailChange ownerChange = EmailChange.builder()
+                    .accountId(AccountId.fromUsername(messageFlagUpdated.getUsername()))
+                    .state(stateFactory.generate())
+                    .date(now)
+                    .isDelegated(false)
+                    .updated(messageFlagUpdated.getMessageIds())
+                    .build();
+
+                Stream<EmailChange> shareeChanges = getSharees(messageFlagUpdated.getMailboxPath(), messageFlagUpdated.getUsername(), mailboxManager)
+                    .map(name -> EmailChange.builder()
+                        .accountId(AccountId.fromString(name))
+                        .state(stateFactory.generate())
+                        .date(now)
+                        .isDelegated(true)
+                        .updated(messageFlagUpdated.getMessageIds())
+                        .build());
+
+                return Stream.concat(Stream.of(ownerChange), shareeChanges)
+                    .collect(Guavate.toImmutableList());
+            }
+            if (event instanceof MailboxListener.Expunged) {
+                MailboxListener.Expunged expunged = (MailboxListener.Expunged) event;
+
+                EmailChange ownerChange = EmailChange.builder()
+                    .accountId(AccountId.fromUsername(expunged.getUsername()))
+                    .state(stateFactory.generate())
+                    .date(now)
+                    .isDelegated(false)
+                    .destroyed(expunged.getMessageIds())
+                    .build();
+
+                Stream<EmailChange> shareeChanges = getSharees(expunged.getMailboxPath(), expunged.getUsername(), mailboxManager)
+                    .map(name -> EmailChange.builder()
+                        .accountId(AccountId.fromString(name))
+                        .state(stateFactory.generate())
+                        .date(now)
+                        .isDelegated(true)
+                        .destroyed(expunged.getMessageIds())
+                        .build());
+
+                return Stream.concat(Stream.of(ownerChange), shareeChanges)
+                    .collect(Guavate.toImmutableList());
+            }
+
+            return ImmutableList.of();
+        }
+    }
+
+    private static Stream<String> getSharees(MailboxPath path, Username username, MailboxManager mailboxManager) {
+        MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
+        try {
+            MailboxACL mailboxACL = mailboxManager.listRights(path, mailboxSession);
+            return mailboxACL.getEntries().keySet()
+                .stream()
+                .filter(rfc4314Rights -> !rfc4314Rights.isNegative())
+                .filter(rfc4314Rights -> rfc4314Rights.getNameType().equals(MailboxACL.NameType.user))
+                .map(MailboxACL.EntryKey::getName);
+        } catch (MailboxException e) {
+            return Stream.of();
+        }
+    }
+
     private final AccountId accountId;
     private final State state;
     private final ZonedDateTime date;
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
index f84e64a..486767b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
@@ -20,7 +20,7 @@
 package org.apache.james.jmap.change
 
 import javax.inject.Inject
-import org.apache.james.jmap.api.change.{MailboxChange, MailboxChangeRepository}
+import org.apache.james.jmap.api.change.{EmailChange, EmailChangeRepository, MailboxChange, MailboxChangeRepository}
 import org.apache.james.mailbox.events.MailboxListener.{MailboxEvent, ReactiveGroupMailboxListener}
 import org.apache.james.mailbox.events.{Event, Group}
 import org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY
@@ -32,12 +32,16 @@ import scala.jdk.CollectionConverters._
 case class MailboxChangeListenerGroup() extends Group {}
 
 case class MailboxChangeListener @Inject() (mailboxChangeRepository: MailboxChangeRepository,
-                                            mailboxChangeFactory: MailboxChange.Factory) extends ReactiveGroupMailboxListener {
+                                            mailboxChangeFactory: MailboxChange.Factory,
+                                            emailChangeRepository: EmailChangeRepository,
+                                            emailChangeFactory: EmailChange.Factory) extends ReactiveGroupMailboxListener {
 
   override def reactiveEvent(event: Event): Publisher[Void] =
     SFlux.fromIterable(mailboxChangeFactory.fromEvent(event).asScala)
       .flatMap(change => mailboxChangeRepository.save(change), DEFAULT_CONCURRENCY)
-      .`then`()
+      .thenMany(SFlux.fromIterable(emailChangeFactory.fromEvent(event).asScala))
+      .flatMap(change => emailChangeRepository.save(change), DEFAULT_CONCURRENCY)
+      .`then`
       .`then`(SMono.empty[Void]).asJava
 
   override def getDefaultGroup: Group = MailboxChangeListenerGroup()
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
index ba6ec97..a2bfcfa 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
@@ -20,22 +20,21 @@
 package org.apache.james.jmap.change
 
 import java.time.{Clock, ZonedDateTime}
-
 import javax.mail.Flags
-import org.apache.james.jmap.api.change.{MailboxChange, MailboxChangeRepository, State}
+import org.apache.james.jmap.api.change.{EmailChange, EmailChangeRepository, MailboxChange, MailboxChangeRepository, State}
 import org.apache.james.jmap.api.model.AccountId
 import org.apache.james.jmap.change.MailboxChangeListenerTest.ACCOUNT_ID
-import org.apache.james.jmap.memory.change.MemoryMailboxChangeRepository
+import org.apache.james.jmap.memory.change.{MemoryEmailChangeRepository, MemoryMailboxChangeRepository}
 import org.apache.james.mailbox.MessageManager.{AppendCommand, AppendResult, FlagsUpdateMode}
 import org.apache.james.mailbox.events.delivery.InVmEventDelivery
 import org.apache.james.mailbox.events.{InVMEventBus, MemoryEventDeadLetters, RetryBackoffConfiguration}
 import org.apache.james.mailbox.fixture.MailboxFixture.{ALICE, BOB}
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources
-import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageRange, TestId}
+import org.apache.james.mailbox.model.{ComposedMessageId, MailboxACL, MailboxId, MailboxPath, MessageRange, TestId, TestMessageId}
 import org.apache.james.mailbox.{MailboxManager, MailboxSessionUtil, MessageManager}
 import org.apache.james.metrics.tests.RecordingMetricFactory
 import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.{BeforeEach, Test}
+import org.junit.jupiter.api.{BeforeEach, Nested, Test}
 
 import scala.jdk.CollectionConverters._
 import scala.jdk.OptionConverters._
@@ -46,9 +45,11 @@ object MailboxChangeListenerTest {
 
 class MailboxChangeListenerTest {
 
-  var repository: MailboxChangeRepository = _
+  var mailboxChangeRepository: MailboxChangeRepository = _
   var mailboxManager: MailboxManager = _
   var mailboxChangeFactory: MailboxChange.Factory = _
+  var emailChangeRepository: EmailChangeRepository = _
+  var emailChangeFactory: EmailChange.Factory = _
   var stateFactory: State.Factory = _
   var listener: MailboxChangeListener = _
   var clock: Clock = _
@@ -66,171 +67,277 @@ class MailboxChangeListenerTest {
     mailboxManager = resources.getMailboxManager
     stateFactory = new State.DefaultFactory
     mailboxChangeFactory = new MailboxChange.Factory(clock, mailboxManager, stateFactory)
-    repository = new MemoryMailboxChangeRepository()
-    listener = MailboxChangeListener(repository, mailboxChangeFactory)
+    mailboxChangeRepository = new MemoryMailboxChangeRepository()
+    emailChangeFactory = new EmailChange.Factory(clock, mailboxManager, stateFactory)
+    emailChangeRepository = new MemoryEmailChangeRepository()
+    listener = MailboxChangeListener(mailboxChangeRepository, mailboxChangeFactory, emailChangeRepository, emailChangeFactory)
     resources.getEventBus.register(listener)
   }
 
-  @Test
-  def createMailboxShouldStoreCreatedEvent(): Unit = {
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getCreated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def updateMailboxNameShouldStoreUpdatedEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val newPath = MailboxPath.forUser(BOB, "another")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    mailboxManager.renameMailbox(path, newPath, mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def updateMailboxACLShouldStoreUpdatedEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.inbox(BOB)
-    val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
-    val state: State = repository.getLatestState(ACCOUNT_ID).block()
-
-    mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def appendMessageToMailboxShouldStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
-
-    mailboxManager
-      .getMailbox(inboxId, mailboxSession)
-      .appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def addSeenFlagsShouldStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-    val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
-    messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
+  @Nested
+  class MailboxChangeEvents {
+    @Test
+    def createMailboxShouldStoreCreatedEvent(): Unit = {
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getCreated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def updateMailboxNameShouldStoreUpdatedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val newPath = MailboxPath.forUser(BOB, "another")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      mailboxManager.renameMailbox(path, newPath, mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def updateMailboxACLShouldStoreUpdatedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.inbox(BOB)
+      val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
+      val state: State = mailboxChangeRepository.getLatestState(ACCOUNT_ID).block()
+
+      mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def appendMessageToMailboxShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
+
+      mailboxManager
+        .getMailbox(inboxId, mailboxSession)
+        .appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def addSeenFlagsShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def removeSeenFlagsShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      messageManager.appendMessage(AppendCommand.builder()
+        .withFlags(new Flags(Flags.Flag.SEEN))
+        .build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.REMOVE, MessageRange.all(), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def addOtherThanSeenFlagsShouldNotStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.ANSWERED), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .isEmpty()
+    }
+
+    @Test
+    def updateOtherThanSeenFlagsShouldNotStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      messageManager.appendMessage(AppendCommand.builder()
+        .withFlags(new Flags(Flags.Flag.ANSWERED))
+        .build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE, MessageRange.all(), mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .isEmpty()
+    }
+
+    @Test
+    def deleteMessageFromMailboxShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+      messageManager.delete(List(appendResult.getId.getUid).asJava, mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def deleteMailboxNameShouldStoreDestroyedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      mailboxManager.deleteMailbox(inboxId, mailboxSession)
+
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getDestroyed)
+        .containsExactly(inboxId)
+    }
   }
 
-  @Test
-  def removeSeenFlagsShouldStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-    val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
-    messageManager.appendMessage(AppendCommand.builder()
-      .withFlags(new Flags(Flags.Flag.SEEN))
-      .build("header: value\r\n\r\nbody"), mailboxSession)
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.REMOVE, MessageRange.all(), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def addOtherThanSeenFlagsShouldNotStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-    val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
-    messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    messageManager.setFlags(new Flags(Flags.Flag.ANSWERED), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .isEmpty()
-  }
-
-  @Test
-  def updateOtherThanSeenFlagsShouldNotStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-    val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
-    messageManager.appendMessage(AppendCommand.builder()
-      .withFlags(new Flags(Flags.Flag.ANSWERED))
-      .build("header: value\r\n\r\nbody"), mailboxSession)
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    messageManager.setFlags(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE, MessageRange.all(), mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .isEmpty()
-  }
-
-  @Test
-  def deleteMessageFromMailboxShouldStoreUpdateEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-    val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
-    val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-    messageManager.delete(List(appendResult.getId.getUid).asJava, mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
-      .containsExactly(inboxId)
-  }
-
-  @Test
-  def deleteMailboxNameShouldStoreDestroyedEvent(): Unit = {
-    val mailboxSession = MailboxSessionUtil.create(BOB)
-    val path = MailboxPath.forUser(BOB, "test")
-    val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
-
-    val state = stateFactory.generate()
-    repository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
-
-    mailboxManager.deleteMailbox(inboxId, mailboxSession)
-
-    assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getDestroyed)
-      .containsExactly(inboxId)
+  @Nested
+  class EmailChangeEvents {
+    @Test
+    def appendMessageToMailboxShouldStoreCreatedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      emailChangeRepository.save(EmailChange.builder()
+          .accountId(ACCOUNT_ID)
+          .state(state)
+          .date(ZonedDateTime.now)
+          .isDelegated(false)
+          .created(TestMessageId.of(0))
+          .build)
+        .block()
+
+      val appendResult: AppendResult = mailboxManager
+        .getMailbox(inboxId, mailboxSession)
+        .appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      assertThat(emailChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getCreated)
+        .containsExactly(appendResult.getId.getMessageId)
+    }
+
+    @Test
+    def addFlagsShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      emailChangeRepository.save(EmailChange.builder()
+          .accountId(ACCOUNT_ID)
+          .state(state)
+          .date(ZonedDateTime.now)
+          .isDelegated(false)
+          .created(TestMessageId.of(0))
+          .build)
+        .block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.ANSWERED), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
+
+      assertThat(emailChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(appendResult.getId.getMessageId)
+    }
+
+    @Test
+    def removeSeenFlagsShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder()
+        .withFlags(new Flags(Flags.Flag.DRAFT))
+        .build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      emailChangeRepository.save(EmailChange.builder()
+          .accountId(ACCOUNT_ID)
+          .state(state)
+          .date(ZonedDateTime.now)
+          .isDelegated(false)
+          .created(TestMessageId.of(0))
+          .build)
+        .block()
+
+      messageManager.setFlags(new Flags(Flags.Flag.DRAFT), FlagsUpdateMode.REMOVE, MessageRange.all(), mailboxSession)
+
+      assertThat(emailChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(appendResult.getId.getMessageId)
+    }
+
+    @Test
+    def deleteMessageFromMailboxShouldStoreUpdateEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+      val messageManager: MessageManager = mailboxManager.getMailbox(inboxId, mailboxSession)
+      val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
+
+      val state = stateFactory.generate()
+      emailChangeRepository.save(EmailChange.builder()
+          .accountId(ACCOUNT_ID)
+          .state(state)
+          .date(ZonedDateTime.now)
+          .isDelegated(false)
+          .created(TestMessageId.of(0))
+          .build)
+        .block()
+
+      messageManager.delete(List(appendResult.getId.getUid).asJava, mailboxSession)
+
+      assertThat(emailChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getDestroyed)
+        .containsExactly(appendResult.getId.getMessageId)
+    }
   }
 }


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


[james-project] 04/05: JAMES-3471 Binding for MemoryEmailChangeRepository

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f91be1a75a0ffa634be1c0f50f43dbbf0178ea99
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Jan 5 10:39:59 2021 +0700

    JAMES-3471 Binding for MemoryEmailChangeRepository
---
 .../java/org/apache/james/modules/mailbox/MemoryMailboxModule.java    | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
index da8950e..a1a47da 100644
--- a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
+++ b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
@@ -25,8 +25,10 @@ import javax.inject.Singleton;
 
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthorizator;
+import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.MailboxChangeRepository;
 import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.memory.change.MemoryEmailChangeRepository;
 import org.apache.james.jmap.memory.change.MemoryMailboxChangeRepository;
 import org.apache.james.mailbox.AttachmentContentLoader;
 import org.apache.james.mailbox.AttachmentManager;
@@ -108,6 +110,7 @@ public class MemoryMailboxModule extends AbstractModule {
         bind(MailboxManager.class).to(InMemoryMailboxManager.class);
         bind(StoreMailboxManager.class).to(InMemoryMailboxManager.class);
         bind(MailboxChangeRepository.class).to(MemoryMailboxChangeRepository.class);
+        bind(EmailChangeRepository.class).to(MemoryEmailChangeRepository.class);
         bind(MessageIdManager.class).to(StoreMessageIdManager.class);
         bind(AttachmentManager.class).to(StoreAttachmentManager.class);
         bind(SessionProvider.class).to(SessionProviderImpl.class);
@@ -129,6 +132,7 @@ public class MemoryMailboxModule extends AbstractModule {
         bind(UserRepositoryAuthorizator.class).in(Scopes.SINGLETON);
         bind(InMemoryMailboxManager.class).in(Scopes.SINGLETON);
         bind(MemoryMailboxChangeRepository.class).in(Scopes.SINGLETON);
+        bind(MemoryEmailChangeRepository.class).in(Scopes.SINGLETON);
         bind(InMemoryMessageId.Factory.class).in(Scopes.SINGLETON);
         bind(StoreMessageIdManager.class).in(Scopes.SINGLETON);
         bind(StoreAttachmentManager.class).in(Scopes.SINGLETON);


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


[james-project] 05/05: JAMES-3457 ADR for Mailbox/changes

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 463516db2f7091f6b48f7158fd3b18ccb824a65e
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 7 15:49:15 2020 +0700

    JAMES-3457 ADR for Mailbox/changes
---
 ...map-push-with-mailbox-changes-implementation.md | 99 ++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/src/adr/0045-support-jmap-push-with-mailbox-changes-implementation.md b/src/adr/0045-support-jmap-push-with-mailbox-changes-implementation.md
new file mode 100644
index 0000000..4c476b4
--- /dev/null
+++ b/src/adr/0045-support-jmap-push-with-mailbox-changes-implementation.md
@@ -0,0 +1,99 @@
+# 45. Support JMAP Push with Mailbox/changes implementation
+
+Date: 2020-12-08
+
+## Status
+
+Accepted (lazy consensus).
+
+## Context
+
+JMAP Push notifications allow clients to efficiently update (almost) instantly to stay in sync with data changes on the server. 
+
+In order to support this, we need to handle the **state** property that comes with JMAP get/set request. This means that James needs to be able 
+to record a new state for objects whenever a change happens as well as return the most recent state to the client when fetching objects. 
+
+First step is to implement Mailbox/changes. 
+
+## Decision
+
+We will implement a mechanism to record all the changes happening to Mailbox objects in the form of a list of **mailboxId**. When an event such as  
+created/updated/destroyed occur, or when message is appended to a mailbox we will store their **mailboxIds** along with a **state** object
+in a Cassandra table.  
+
+Each state will have a list of changes, and all the **mailboxId** will be stored as separated lists corresponding to the change which they represent: **created**, **updated**, **destroyed**.
+For the case when messages are appended to a mailbox, it will be counted as an updated event and that mailboxId should be stored in **updated** list. 
+
+Leveraging the **MailboxChanges** table, we can now fetch all the changes that have occurred since a particular **state**.
+
+States are stored in Cassandra as time based UUID (**TimeUUID**). This ensures that no conflicting changes will happen in the case when two or more events occur at the same point in time.
+**TimeUUID** also allows **state** to be sorted in chronological order.
+
+Components that need to be implemented:
+
+- MailboxChangesRepository: Allows storing and fetching the **state** along with the lists of **mailboxId** in **MailboxChanges** table.
+- MailboxChangeListener: Listens to changes and triggers the record creation in **MailboxChanges** table.
+- MailboxChangeMethod: Handles the **state** property, allowing client to fetch the changes since a particular state. 
+- MailboxSetMethod/MailboxGetMethod needs to query the MailboxChangesRepository for their states properties.
+ 
+## Example of a Mailbox/changes request/response
+
+**Request**
+
+```
+[["Mailbox/changes", {
+  "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+  "sinceState": "dd3721a6-b3ee-4884-8762-fccc0c576438"
+}, "t0"]]
+```
+
+**Response**
+
+```
+[["Mailbox/changes", {
+  "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+  "oldState": "dd3721a6-b3ee-4884-8762-fccc0c576438",
+  "newState": "2433b670-3554-4f55-bab1-147848e89e5d",
+  "hasMoreChanges": false,
+  "created": [ 
+    "1", 
+    "2" 
+  ],
+ "updated": [],
+ "destroyed": []
+}, "t0" ]]
+```
+
+## Consequences
+
+- Due to the limitation of the event listening mechanism of the listeners, we can only store one change (one **mailboxId**) for each state instead of many.  
+However, by keeping the data type of changes as separated lists, we will be more opened for future improvements.        
+- Changes can only be fetched in a linear fashion from oldest to newest, as opposed to how it should prioritize newer changes first according to the spec.
+
+## Cassandra table structure
+
+Only one table is required:
+
+```
+TABLE mailbox_changes
+PRIMARY KEY accountId
+CLUSTERING COLUMN state
+COLUMN created
+COLUMN updated
+COLUMN destroyed
+COLUMN isCountChange
+ORDERED BY state
+```
+
+## References
+
+- [Support JMAP HTTP PUSH](https://issues.apache.org/jira/browse/JAMES-3457)
+- [Implement a MailboxChangeRepository](https://issues.apache.org/jira/browse/JAMES-3459)
+- [Implement a JMAP MailboxChangeListener](https://issues.apache.org/jira/browse/JAMES-3460)
+- [Implement Mailbox/changes method and related contract tests](https://issues.apache.org/jira/browse/JAMES-3461)
+- [Implement CassandraMailboxChangeRepository](https://issues.apache.org/jira/browse/JAMES-3462)
+- [Mailbox/get should handle state property](https://issues.apache.org/jira/browse/JAMES-3463)
+- [Mailbox/set should handle oldState & newState](https://issues.apache.org/jira/browse/JAMES-3464)
+- [Mailbox/changes updatedProperties handling](https://issues.apache.org/jira/browse/JAMES-3465)
+- [ADR for Mailbox/changes](https://github.com/apache/james-project/pull/276)
+


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