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 2020/11/30 12:12:27 UTC

[james-project] 01/07: JAMES-3459 Mailbox/changes MemoryMailboxChangeRepository implementation

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 6c4d39222915334f7c48861c62ad92380d5daec7
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Wed Nov 25 17:54:51 2020 +0700

    JAMES-3459 Mailbox/changes MemoryMailboxChangeRepository implementation
---
 .../apache/james/jmap/draft/JmapGuiceProbe.java    |   2 +-
 .../vacation/CassandraNotificationRegistry.java    |   2 +-
 .../vacation/CassandraNotificationRegistryDAO.java |   2 +-
 .../cassandra/vacation/CassandraVacationDAO.java   |   2 +-
 .../vacation/CassandraVacationRepository.java      |   2 +-
 .../james/jmap/api/change/MailboxChange.java       | 124 ++++++++++
 .../MailboxChangeRepository.java}                  |  16 +-
 .../james/jmap/api/change/MailboxChanges.java      | 166 +++++++++++++
 .../ChangeNotFoundException.java}                  |  22 +-
 .../jmap/api/{vacation => model}/AccountId.java    |   2 +-
 .../jmap/api/vacation/NotificationRegistry.java    |   2 +
 .../jmap/api/vacation/VacationRepository.java      |   2 +
 .../change/MemoryMailboxChangeRepository.java      |  77 ++++++
 .../vacation/MemoryNotificationRegistry.java       |   2 +-
 .../memory/vacation/MemoryVacationRepository.java  |   2 +-
 .../change/MailboxChangeRepositoryContract.java    | 269 +++++++++++++++++++++
 .../james/jmap/api/vacation/AccountIdTest.java     |   1 +
 .../api/vacation/NotificationRegistryContract.java |   1 +
 .../api/vacation/VacationRepositoryContract.java   |   1 +
 .../change/MemoryMailboxChangeRepositoryTest.java} |  22 +-
 .../apache/james/jmap/VacationIntegrationTest.java |   2 +-
 .../james/jmap/VacationRelayIntegrationTest.java   |   2 +-
 .../integration/GetVacationResponseTest.java       |   2 +-
 .../integration/SetVacationResponseTest.java       |   2 +-
 .../draft/methods/GetVacationResponseMethod.java   |   2 +-
 .../draft/methods/SetVacationResponseMethod.java   |   2 +-
 .../apache/james/jmap/mailet/VacationMailet.java   |   2 +-
 .../methods/GetVacationResponseMethodTest.java     |   2 +-
 .../methods/SetVacationResponseMethodTest.java     |   2 +-
 .../james/jmap/mailet/VacationMailetTest.java      |   2 +-
 .../VacationResponseGetMethodContract.scala        |   3 +-
 .../jmap/method/VacationResponseGetMethod.scala    |   3 +-
 .../jmap/method/VacationResponseSetMethod.scala    |   3 +-
 33 files changed, 700 insertions(+), 48 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
index 77ef5bc..6347e36 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
@@ -25,8 +25,8 @@ import javax.inject.Inject;
 
 import org.apache.james.core.Username;
 import org.apache.james.jmap.JMAPServer;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
-import org.apache.james.jmap.api.vacation.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.api.vacation.VacationRepository;
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistry.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistry.java
index 4fa556f..4f564f8 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistry.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistry.java
@@ -25,7 +25,7 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.util.date.ZonedDateTimeProvider;
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistryDAO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistryDAO.java
index bf297ce..508b12f 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistryDAO.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraNotificationRegistryDAO.java
@@ -31,7 +31,7 @@ import java.util.Optional;
 import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.jmap.cassandra.vacation.tables.CassandraNotificationTable;
 
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationDAO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationDAO.java
index 36d0562..5a3e472 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationDAO.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationDAO.java
@@ -34,7 +34,7 @@ import javax.inject.Inject;
 import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
 import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.cassandra.vacation.tables.CassandraVacationTable;
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepository.java
index 06fc937..a3a24bb 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepository.java
@@ -21,7 +21,7 @@ package org.apache.james.jmap.cassandra.vacation;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.api.vacation.VacationRepository;
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
new file mode 100644
index 0000000..3bb849e
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
@@ -0,0 +1,124 @@
+/****************************************************************
+ * 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.api.change;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.model.MailboxId;
+
+public class MailboxChange {
+
+    public static class State {
+
+        public static State of(UUID value) {
+            return new State(value);
+        }
+
+        private final UUID value;
+
+        private State(UUID value) {
+            this.value = value;
+        }
+
+        public UUID getValue() {
+            return value;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof State) {
+                State state = (State) o;
+
+                return Objects.equals(this.value, state.value);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(value);
+        }
+    }
+
+    public static class Limit {
+
+        public static Limit of(int value) {
+            return new Limit(value);
+        }
+
+        private final int value;
+
+        private Limit(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public static MailboxChange of(AccountId accountId, State state,  ZonedDateTime date, List<MailboxId> created, List<MailboxId> updated, List<MailboxId> destroyed) {
+        return new MailboxChange(accountId, state, date, created, updated, destroyed);
+    }
+
+    private final AccountId accountId;
+    private final State state;
+    private final ZonedDateTime date;
+    private final List<MailboxId> created;
+    private final List<MailboxId> updated;
+    private final List<MailboxId> destroyed;
+
+    private MailboxChange(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> created, List<MailboxId> updated, List<MailboxId> destroyed) {
+        this.accountId = accountId;
+        this.state = state;
+        this.date = date;
+        this.created = created;
+        this.updated = updated;
+        this.destroyed = destroyed;
+    }
+
+    public AccountId getAccountId() {
+        return accountId;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public ZonedDateTime getDate() {
+        return date;
+    }
+
+    public List<MailboxId> getCreated() {
+        return created;
+    }
+
+    public List<MailboxId> getUpdated() {
+        return updated;
+    }
+
+    public List<MailboxId> getDestroyed() {
+        return destroyed;
+    }
+}
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
similarity index 75%
copy from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
copy to server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
index 1526ca4..fb965be 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
@@ -17,19 +17,19 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.api.vacation;
+package org.apache.james.jmap.api.change;
 
-import java.time.ZonedDateTime;
 import java.util.Optional;
 
-import reactor.core.publisher.Mono;
-
-public interface NotificationRegistry {
+import org.apache.james.jmap.api.change.MailboxChange.Limit;
+import org.apache.james.jmap.api.change.MailboxChange.State;
+import org.apache.james.jmap.api.model.AccountId;
 
-    Mono<Void> register(AccountId accountId, RecipientId recipientId, Optional<ZonedDateTime> expiryDate);
+import reactor.core.publisher.Mono;
 
-    Mono<Boolean> isRegistered(AccountId accountId, RecipientId recipientId);
+public interface MailboxChangeRepository {
 
-    Mono<Void> flush(AccountId accountId);
+    Mono<Void> save(MailboxChange change);
 
+    Mono<MailboxChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxIdsToReturn);
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChanges.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChanges.java
new file mode 100644
index 0000000..0dc1bc9
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChanges.java
@@ -0,0 +1,166 @@
+/****************************************************************
+ * 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.api.change;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.james.mailbox.model.MailboxId;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+public class MailboxChanges {
+
+    public static class MailboxChangesBuilder {
+
+        public static class MailboxChangeCollector implements Collector<MailboxChange, MailboxChangesBuilder, MailboxChanges> {
+            private final MailboxChange.Limit limit;
+
+            public MailboxChangeCollector(MailboxChange.Limit limit) {
+                this.limit = limit;
+            }
+
+            @Override
+            public Supplier<MailboxChangesBuilder> supplier() {
+                return () -> new MailboxChangesBuilder(limit);
+            }
+
+            public BiConsumer<MailboxChangesBuilder, MailboxChange> accumulator() {
+                return MailboxChangesBuilder::add;
+            }
+
+            @Override
+            public BinaryOperator<MailboxChangesBuilder> combiner() {
+                throw new NotImplementedException("Not supported");
+            }
+
+            @Override
+            public Function<MailboxChangesBuilder, MailboxChanges> finisher() {
+                return MailboxChangesBuilder::build;
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Sets.immutableEnumSet(Characteristics.UNORDERED);
+            }
+        }
+
+        private MailboxChange.State state;
+        private boolean hasMoreChanges;
+        private boolean canAddMoreItem;
+        private MailboxChange.Limit limit;
+        private Set<MailboxId> created;
+        private Set<MailboxId> updated;
+        private Set<MailboxId> destroyed;
+
+        public MailboxChangesBuilder(MailboxChange.Limit limit) {
+            this.limit = limit;
+            this.hasMoreChanges = false;
+            this.canAddMoreItem = true;
+            this.created = new HashSet<>();
+            this.updated = new HashSet<>();
+            this.destroyed = new HashSet<>();
+        }
+
+        public MailboxChanges.MailboxChangesBuilder add(MailboxChange change) {
+            if (!canAddMoreItem) {
+                return this;
+            }
+
+            Set<MailboxId> createdTemp = new HashSet<>(created);
+            Set<MailboxId> updatedTemp = new HashSet<>(updated);
+            Set<MailboxId> destroyedTemp = new HashSet<>(destroyed);
+            createdTemp.addAll(change.getCreated());
+            updatedTemp.addAll(change.getUpdated());
+            destroyedTemp.addAll(change.getDestroyed());
+
+            if (createdTemp.size() + updatedTemp.size() + destroyedTemp.size() > limit.getValue()) {
+                hasMoreChanges = true;
+                canAddMoreItem = false;
+                return this;
+            }
+
+            state = change.getState();
+            this.created.addAll(change.getCreated());
+            this.updated.addAll(change.getUpdated());
+            this.destroyed.addAll(change.getDestroyed());
+
+            return this;
+        }
+
+        public MailboxChanges build() {
+            return new MailboxChanges(state, hasMoreChanges, created, updated, destroyed);
+        }
+    }
+
+    private MailboxChange.State newState;
+    private final boolean hasMoreChanges;
+    private final Set<MailboxId> created;
+    private final Set<MailboxId> updated;
+    private final Set<MailboxId> destroyed;
+
+    private MailboxChanges(MailboxChange.State newState, boolean hasMoreChanges, Set<MailboxId> created, Set<MailboxId> updated, Set<MailboxId> destroyed) {
+        this.newState = newState;
+        this.hasMoreChanges = hasMoreChanges;
+        this.created = created;
+        this.updated = updated;
+        this.destroyed = destroyed;
+    }
+
+    public MailboxChangesBuilder build(MailboxChange.Limit limit) {
+        return new MailboxChangesBuilder(limit);
+    }
+
+    public MailboxChange.State getNewState() {
+        return newState;
+    }
+
+    public boolean hasMoreChanges() {
+        return hasMoreChanges;
+    }
+
+    public Set<MailboxId> getCreated() {
+        return created;
+    }
+
+    public Set<MailboxId> getUpdated() {
+        return updated;
+    }
+
+    public Set<MailboxId> getDestroyed() {
+        return destroyed;
+    }
+
+    public List<MailboxId> getAllChanges() {
+        return ImmutableList.<MailboxId>builder()
+            .addAll(created)
+            .addAll(updated)
+            .addAll(destroyed)
+            .build();
+    }
+}
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/exception/ChangeNotFoundException.java
similarity index 74%
copy from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
copy to server/data/data-jmap/src/main/java/org/apache/james/jmap/api/exception/ChangeNotFoundException.java
index 1526ca4..9fcc4a0 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/exception/ChangeNotFoundException.java
@@ -17,19 +17,19 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.api.vacation;
+package org.apache.james.jmap.api.exception;
 
-import java.time.ZonedDateTime;
-import java.util.Optional;
+import org.apache.james.jmap.api.change.MailboxChange;
 
-import reactor.core.publisher.Mono;
+public class ChangeNotFoundException extends RuntimeException {
+    private final MailboxChange.State state;
 
-public interface NotificationRegistry {
-
-    Mono<Void> register(AccountId accountId, RecipientId recipientId, Optional<ZonedDateTime> expiryDate);
-
-    Mono<Boolean> isRegistered(AccountId accountId, RecipientId recipientId);
-
-    Mono<Void> flush(AccountId accountId);
+    public ChangeNotFoundException(MailboxChange.State state, String msg) {
+        super(msg);
+        this.state = state;
+    }
 
+    public MailboxChange.State getState() {
+        return state;
+    }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/AccountId.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/AccountId.java
similarity index 98%
rename from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/AccountId.java
rename to server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/AccountId.java
index 59b181a..4f6417c 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/AccountId.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/AccountId.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.api.vacation;
+package org.apache.james.jmap.api.model;
 
 import java.util.Objects;
 
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
index 1526ca4..b6f5a58 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/NotificationRegistry.java
@@ -22,6 +22,8 @@ package org.apache.james.jmap.api.vacation;
 import java.time.ZonedDateTime;
 import java.util.Optional;
 
+import org.apache.james.jmap.api.model.AccountId;
+
 import reactor.core.publisher.Mono;
 
 public interface NotificationRegistry {
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
index fe58e49..9077da7 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.jmap.api.vacation;
 
+import org.apache.james.jmap.api.model.AccountId;
+
 import reactor.core.publisher.Mono;
 
 public interface VacationRepository {
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java
new file mode 100644
index 0000000..e920904
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java
@@ -0,0 +1,77 @@
+/****************************************************************
+ * 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.memory.change;
+
+import java.util.Comparator;
+import java.util.Optional;
+
+import org.apache.james.jmap.api.change.MailboxChange;
+import org.apache.james.jmap.api.change.MailboxChange.Limit;
+import org.apache.james.jmap.api.change.MailboxChange.State;
+import org.apache.james.jmap.api.change.MailboxChangeRepository;
+import org.apache.james.jmap.api.change.MailboxChanges;
+import org.apache.james.jmap.api.change.MailboxChanges.MailboxChangesBuilder.MailboxChangeCollector;
+import org.apache.james.jmap.api.exception.ChangeNotFoundException;
+import org.apache.james.jmap.api.model.AccountId;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class MemoryMailboxChangeRepository implements MailboxChangeRepository {
+
+    public static final Limit DEFAULT_NUMBER_OF_CHANGES = Limit.of(5);
+    private final Multimap<AccountId, MailboxChange> mailboxChangeMap;
+
+    public MemoryMailboxChangeRepository() {
+        this.mailboxChangeMap = ArrayListMultimap.create();
+    }
+
+    @Override
+    public Mono<Void> save(MailboxChange change) {
+        Preconditions.checkNotNull(change.getAccountId());
+        Preconditions.checkNotNull(change.getState());
+
+        return Mono.just(mailboxChangeMap.put(change.getAccountId(), change)).then();
+    }
+
+    @Override
+    public Mono<MailboxChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxChanges) {
+        Preconditions.checkNotNull(accountId);
+        Preconditions.checkNotNull(state);
+        maxChanges.ifPresent(limit -> Preconditions.checkArgument(limit.getValue() > 0, "maxChanges must be a positive integer"));
+
+        return findByState(accountId, state)
+            .flatMapMany(currentState -> Flux.fromIterable(mailboxChangeMap.get(accountId))
+                .filter(change -> change.getDate().isAfter(currentState.getDate()))
+                .sort(Comparator.comparing(MailboxChange::getDate)))
+            .collect(new MailboxChangeCollector(maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
+    }
+
+    private Mono<MailboxChange> findByState(AccountId accountId, State state) {
+        return Flux.fromIterable(mailboxChangeMap.get(accountId))
+            .filter(change -> change.getState().equals(state))
+            .switchIfEmpty(Mono.error(new ChangeNotFoundException(state, String.format("State '%s' could not be found", state.getValue()))))
+            .single();
+    }
+}
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryNotificationRegistry.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryNotificationRegistry.java
index 315680e..914bbb2 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryNotificationRegistry.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryNotificationRegistry.java
@@ -24,7 +24,7 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.util.date.ZonedDateTimeProvider;
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryVacationRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryVacationRepository.java
index 82e3b14..3948d05 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryVacationRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/vacation/MemoryVacationRepository.java
@@ -22,7 +22,7 @@ package org.apache.james.jmap.memory.vacation;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.api.vacation.VacationRepository;
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
new file mode 100644
index 0000000..66f27e3
--- /dev/null
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
@@ -0,0 +1,269 @@
+/****************************************************************
+ * 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.api.change;
+
+import static org.apache.james.mailbox.fixture.MailboxFixture.BOB;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apache.james.jmap.api.change.MailboxChange.Limit;
+import org.apache.james.jmap.api.change.MailboxChange.State;
+import org.apache.james.jmap.api.exception.ChangeNotFoundException;
+import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.model.TestId;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public interface MailboxChangeRepositoryContract {
+    AccountId ACCOUNT_ID = AccountId.fromUsername(BOB);
+    ZonedDateTime DATE = ZonedDateTime.now();
+    State STATE_0 = State.of(UUID.randomUUID());
+
+    MailboxChangeRepository mailboxChangeRepository();
+
+    @Test
+    default void saveChangeShouldSuccess() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange change = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+
+        assertThatCode(() -> repository.save(change))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    default void saveChangeShouldFailWhenNoAccountId() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange change = MailboxChange.of(null, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+
+        assertThatThrownBy(() -> repository.save(change).block())
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    default void saveChangeShouldFailWhenNoState() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange change = MailboxChange.of(ACCOUNT_ID, null, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+
+        assertThatThrownBy(() -> repository.save(change).block())
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    default void getChangesShouldSuccess() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(1), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(1)), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+            .hasSameElementsAs(change.getUpdated());
+    }
+
+    @Test
+    default void getChangesShouldReturnEmptyWhenNoNewerState() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+            .isEmpty();
+    }
+
+    @Test
+    default void getChangesShouldLimitChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+        repository.save(change3);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getCreated())
+            .containsExactlyInAnyOrder(TestId.of(2), TestId.of(3), TestId.of(4));
+    }
+
+    @Test
+    default void getChangesShouldLimitChangesWhenMaxChangesOmitted() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5), TestId.of(6)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(7)), ImmutableList.of(), ImmutableList.of());
+
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+            .hasSameElementsAs(change1.getCreated());
+    }
+
+    @Test
+    default void getChangesShouldNotReturnMoreThanMaxChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getAllChanges())
+            .hasSameElementsAs(change1.getCreated());
+    }
+
+    @Test
+    default void getChangesShouldReturnEmptyWhenNumberOfChangesExceedMaxChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().getAllChanges())
+            .isEmpty();
+    }
+
+    @Test
+    default void getChangesShouldReturnNewState() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
+            .isEqualTo(change2.getState());
+    }
+
+    @Test
+    default void hasMoreChangesShouldBeTrueWhenMoreChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().hasMoreChanges())
+            .isTrue();
+    }
+
+    @Test
+    default void hasMoreChangesShouldBeFalseWhenNoMoreChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(4))).block().hasMoreChanges())
+            .isFalse();
+    }
+
+    @Test
+    default void changesShouldBeStoredInTheirRespectiveType() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(TestId.of(4)));
+        MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(8)), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(5)));
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+        repository.save(change3);
+
+        MailboxChanges mailboxChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(mailboxChanges.getCreated()).containsExactlyInAnyOrder(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5), TestId.of(6), TestId.of(7), TestId.of(8));
+            softly.assertThat(mailboxChanges.getUpdated()).containsExactlyInAnyOrder(TestId.of(2), TestId.of(3), TestId.of(6), TestId.of(7));
+            softly.assertThat(mailboxChanges.getDestroyed()).containsExactlyInAnyOrder(TestId.of(4), TestId.of(5));
+        });
+    }
+
+    @Test
+    default void getChangesShouldIgnoreDuplicatedValues() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(3)), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
+
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+
+        MailboxChanges mailboxChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block();
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(mailboxChanges.getUpdated()).containsExactly(TestId.of(1), TestId.of(2));
+            softly.assertThat(mailboxChanges.getCreated()).containsExactly(TestId.of(3));
+        });
+    }
+
+    @Test
+    default void getChangesShouldFailWhenInvalidMaxChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange currentState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+        repository.save(currentState);
+        repository.save(change);
+
+        assertThatThrownBy(() -> repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(-1))))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void getChangesShouldFailWhenSinceStateNotFound() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        assertThatThrownBy(() -> repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block())
+            .isInstanceOf(ChangeNotFoundException.class);
+    }
+}
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AccountIdTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AccountIdTest.java
index ac4aa25..1870a49 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AccountIdTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/AccountIdTest.java
@@ -22,6 +22,7 @@ package org.apache.james.jmap.api.vacation;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import org.apache.james.jmap.api.model.AccountId;
 import org.junit.jupiter.api.Test;
 
 class AccountIdTest {
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/NotificationRegistryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/NotificationRegistryContract.java
index afd2c64..0d9d986 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/NotificationRegistryContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/NotificationRegistryContract.java
@@ -28,6 +28,7 @@ import java.time.ZonedDateTime;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.util.date.ZonedDateTimeProvider;
 import org.junit.jupiter.api.Test;
 
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationRepositoryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationRepositoryContract.java
index 436e13a..23d5770 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationRepositoryContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/vacation/VacationRepositoryContract.java
@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.time.ZonedDateTime;
 import java.util.Optional;
 
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.util.ValuePatch;
 import org.junit.jupiter.api.Test;
 
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepositoryTest.java
similarity index 66%
copy from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
copy to server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepositoryTest.java
index fe58e49..9079e65 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/vacation/VacationRepository.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepositoryTest.java
@@ -17,16 +17,22 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.api.vacation;
+package org.apache.james.jmap.memory.change;
 
-import reactor.core.publisher.Mono;
+import org.apache.james.jmap.api.change.MailboxChangeRepository;
+import org.apache.james.jmap.api.change.MailboxChangeRepositoryContract;
+import org.junit.jupiter.api.BeforeEach;
 
-public interface VacationRepository {
+public class MemoryMailboxChangeRepositoryTest implements MailboxChangeRepositoryContract {
+    MailboxChangeRepository mailboxChangeRepository;
 
-    Vacation DEFAULT_VACATION = Vacation.builder().enabled(false).build();
-
-    Mono<Void> modifyVacation(AccountId accountId, VacationPatch vacationPatch);
-
-    Mono<Vacation> retrieveVacation(AccountId accountId);
+    @BeforeEach
+    void setup() {
+        mailboxChangeRepository = new MemoryMailboxChangeRepository();
+    }
 
+    @Override
+    public MailboxChangeRepository mailboxChangeRepository() {
+        return mailboxChangeRepository;
+    }
 }
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationIntegrationTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationIntegrationTest.java
index ff7c010..9f72d73 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationIntegrationTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationIntegrationTest.java
@@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.core.Username;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.junit.categories.BasicFeature;
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationRelayIntegrationTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationRelayIntegrationTest.java
index 38d7344..77ad8a4 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationRelayIntegrationTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/VacationRelayIntegrationTest.java
@@ -30,7 +30,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.commons.net.smtp.SMTPClient;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.dnsservice.api.InMemoryDNSService;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.junit.categories.BasicFeature;
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetVacationResponseTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetVacationResponseTest.java
index 3139ef0..dd150be 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetVacationResponseTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetVacationResponseTest.java
@@ -38,7 +38,7 @@ import java.time.ZonedDateTime;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.jmap.AccessToken;
 import org.apache.james.jmap.FixedDateZonedDateTimeProvider;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.junit.categories.BasicFeature;
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetVacationResponseTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetVacationResponseTest.java
index bf69793..0180c11 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetVacationResponseTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetVacationResponseTest.java
@@ -36,7 +36,7 @@ import java.util.Optional;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.core.Username;
 import org.apache.james.jmap.AccessToken;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationPatch;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethod.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethod.java
index e47af51..df9aae3 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethod.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethod.java
@@ -23,7 +23,7 @@ import static org.apache.james.jmap.http.LoggingHelper.jmapAction;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
 import org.apache.james.jmap.draft.model.GetVacationRequest;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethod.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethod.java
index e8f2839..3b41a6c 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethod.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethod.java
@@ -24,7 +24,7 @@ import static org.apache.james.util.ReactorUtils.context;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
index aea13e6..f5fd41f 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
@@ -27,7 +27,7 @@ import javax.mail.MessagingException;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.MailAddress;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.jmap.api.vacation.Vacation;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethodTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethodTest.java
index 22ad61d..35c05ac 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethodTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetVacationResponseMethodTest.java
@@ -28,7 +28,7 @@ import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.james.core.Username;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
 import org.apache.james.jmap.draft.model.GetMailboxesRequest;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethodTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethodTest.java
index f8c9c20..04386b6 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethodTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetVacationResponseMethodTest.java
@@ -31,7 +31,7 @@ import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.james.core.Username;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
index 4d36816..971628f 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
@@ -33,7 +33,7 @@ import java.util.Optional;
 import javax.mail.MessagingException;
 
 import org.apache.james.core.MailAddress;
-import org.apache.james.jmap.api.vacation.AccountId;
+import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.jmap.api.vacation.Vacation;
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/VacationResponseGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationResponseGetMethodContract.scala
index 1ca2caf..0e39b11 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationResponseGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationResponseGetMethodContract.scala
@@ -27,7 +27,8 @@ import io.restassured.http.ContentType.JSON
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
-import org.apache.james.jmap.api.vacation.{AccountId, VacationPatch}
+import org.apache.james.jmap.api.model.AccountId
+import org.apache.james.jmap.api.vacation.VacationPatch
 import org.apache.james.jmap.draft.JmapGuiceProbe
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
index 926f5e0..b06821b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
@@ -21,7 +21,8 @@ package org.apache.james.jmap.method
 
 import eu.timepit.refined.auto._
 import javax.inject.Inject
-import org.apache.james.jmap.api.vacation.{VacationRepository, AccountId => JavaAccountId}
+import org.apache.james.jmap.api.model.{AccountId => JavaAccountId}
+import org.apache.james.jmap.api.vacation.VacationRepository
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodCallId, MethodName}
 import org.apache.james.jmap.core.State.INSTANCE
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
index 56bd506..8cb940a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
@@ -21,7 +21,8 @@ package org.apache.james.jmap.method
 
 import eu.timepit.refined.auto._
 import javax.inject.Inject
-import org.apache.james.jmap.api.vacation.{AccountId, VacationPatch, VacationRepository}
+import org.apache.james.jmap.api.model.AccountId
+import org.apache.james.jmap.api.vacation.{VacationPatch, VacationRepository}
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.core.SetError.SetErrorDescription


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