You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ad...@apache.org on 2019/07/04 08:33:37 UTC
[james-project] 09/13: JAMES-2807 Implement Memory
DeletedMessageMetadataVault and related contract
This is an automated email from the ASF dual-hosted git repository.
aduprat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 5063bfca0d28010a508fa7b132a0b6b4b199ab7d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 25 14:14:26 2019 +0700
JAMES-2807 Implement Memory DeletedMessageMetadataVault and related contract
---
.../pom.xml | 51 +++++-
.../vault/MemoryDeletedMessageMetadataVault.java | 68 ++++++-
.../MemoryDeletedMessageMetadataVaultTest.java} | 22 ++-
.../DeletedMessageWithStorageInformation.java | 16 +-
.../vault/DeletedMessageMetadataVaultContract.java | 198 +++++++++++++++++++++
.../vault/DeletedMessageVaultMetadataFixture.java | 1 +
6 files changed, 331 insertions(+), 25 deletions(-)
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore-memory/pom.xml b/mailbox/plugin/deleted-messages-vault-blobstore-memory/pom.xml
index d5ae54e..faf54ee 100644
--- a/mailbox/plugin/deleted-messages-vault-blobstore-memory/pom.xml
+++ b/mailbox/plugin/deleted-messages-vault-blobstore-memory/pom.xml
@@ -31,10 +31,57 @@
<dependencies>
<dependency>
- <groupId>org.apache.james</groupId>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>apache-james-mailbox-api</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>apache-james-mailbox-deleted-messages-vault</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>apache-james-mailbox-deleted-messages-vault-blobstore</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
<artifactId>apache-james-mailbox-deleted-messages-vault-blobstore</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>apache-james-mailbox-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>apache-james-mailbox-memory</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.platform</groupId>
+ <artifactId>junit-platform-launcher</artifactId>
+ <scope>test</scope>
</dependency>
</dependencies>
-
</project>
\ No newline at end of file
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/main/java/org/apache/james/vault/MemoryDeletedMessageMetadataVault.java b/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/main/java/org/apache/james/vault/MemoryDeletedMessageMetadataVault.java
index 33f11f6..4bafe68 100644
--- a/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/main/java/org/apache/james/vault/MemoryDeletedMessageMetadataVault.java
+++ b/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/main/java/org/apache/james/vault/MemoryDeletedMessageMetadataVault.java
@@ -19,40 +19,94 @@
package org.apache.james.vault;
-import org.apache.commons.lang3.NotImplementedException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
import org.apache.james.blob.api.BucketName;
import org.apache.james.core.User;
import org.apache.james.mailbox.model.MessageId;
import org.reactivestreams.Publisher;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Table;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
public class MemoryDeletedMessageMetadataVault implements DeletedMessageMetadataVault {
+ private final Table<BucketName, User, Map<MessageId, DeletedMessageWithStorageInformation>> table;
+
+ public MemoryDeletedMessageMetadataVault() {
+ table = HashBasedTable.create();
+ }
+
@Override
public Publisher<Void> store(DeletedMessageWithStorageInformation deletedMessage) {
- throw new NotImplementedException("Not yet implemented");
+ BucketName bucketName = deletedMessage.getStorageInformation().getBucketName();
+ User owner = deletedMessage.getDeletedMessage().getOwner();
+ MessageId messageId = deletedMessage.getDeletedMessage().getMessageId();
+
+ return Mono.fromRunnable(() -> {
+ synchronized (table) {
+ Map<MessageId, DeletedMessageWithStorageInformation> userVault = userVault(bucketName, owner);
+ userVault.put(messageId, deletedMessage);
+ table.put(bucketName, owner, userVault);
+ }
+ });
}
@Override
public Publisher<Void> removeBucket(BucketName bucketName) {
- throw new NotImplementedException("Not yet implemented");
+ return Mono.fromRunnable(() -> {
+ synchronized (table) {
+ table.row(bucketName).clear();
+ }
+ });
}
@Override
public Publisher<Void> remove(BucketName bucketName, User user, MessageId messageId) {
- throw new NotImplementedException("Not yet implemented");
+ return Mono.fromRunnable(() -> {
+ synchronized (table) {
+ userVault(bucketName, user).remove(messageId);
+ }
+ });
}
@Override
public Publisher<StorageInformation> retrieveStorageInformation(User user, MessageId messageId) {
- throw new NotImplementedException("Not yet implemented");
+ return Flux.from(listBuckets())
+ .concatMap(bucket -> {
+ synchronized (table) {
+ return Mono.justOrEmpty(userVault(bucket, user).get(messageId));
+ }
+ })
+ .map(DeletedMessageWithStorageInformation::getStorageInformation)
+ .next();
}
@Override
public Publisher<DeletedMessageWithStorageInformation> listMessages(BucketName bucketName, User user) {
- throw new NotImplementedException("Not yet implemented");
+ synchronized (table) {
+ return Flux.fromIterable(Optional.ofNullable(table.get(bucketName, user))
+ .map(Map::values)
+ .map(ImmutableList::copyOf)
+ .orElse(ImmutableList.of()));
+ }
}
@Override
public Publisher<BucketName> listBuckets() {
- throw new NotImplementedException("Not yet implemented");
+ synchronized (table) {
+ return Flux.fromIterable(ImmutableSet.copyOf(table.rowKeySet()));
+ }
+ }
+
+ private Map<MessageId, DeletedMessageWithStorageInformation> userVault(BucketName bucketName, User owner) {
+ return Optional.ofNullable(table.get(bucketName, owner))
+ .orElse(new HashMap<>());
}
}
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java b/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/test/java/org/apache/james/vault/MemoryDeletedMessageMetadataVaultTest.java
similarity index 71%
copy from mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java
copy to mailbox/plugin/deleted-messages-vault-blobstore-memory/src/test/java/org/apache/james/vault/MemoryDeletedMessageMetadataVaultTest.java
index a7a4db8..5a5cb5a 100644
--- a/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java
+++ b/mailbox/plugin/deleted-messages-vault-blobstore-memory/src/test/java/org/apache/james/vault/MemoryDeletedMessageMetadataVaultTest.java
@@ -19,12 +19,18 @@
package org.apache.james.vault;
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.HashBlobId;
+import org.junit.jupiter.api.BeforeEach;
-public interface DeletedMessageVaultMetadataFixture {
- BlobId BLOB_ID = new HashBlobId.Factory().from("05dcb33b-8382-4744-923a-bc593ad84d23");
- BucketName BUCKET_NAME = BucketName.of("bucket-2019-06-01");
- StorageInformation STORAGE_INFORMATION = new StorageInformation(BUCKET_NAME, BLOB_ID);
-}
+public class MemoryDeletedMessageMetadataVaultTest implements DeletedMessageMetadataVaultContract {
+ private MemoryDeletedMessageMetadataVault memoryDeletedMessageMetadataVault;
+
+ @BeforeEach
+ void setUp() {
+ memoryDeletedMessageMetadataVault = new MemoryDeletedMessageMetadataVault();
+ }
+
+ @Override
+ public DeletedMessageMetadataVault metadataVault() {
+ return memoryDeletedMessageMetadataVault;
+ }
+}
\ No newline at end of file
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore/src/main/java/org/apache/james/vault/DeletedMessageWithStorageInformation.java b/mailbox/plugin/deleted-messages-vault-blobstore/src/main/java/org/apache/james/vault/DeletedMessageWithStorageInformation.java
index 669b3a6..1c17a38 100644
--- a/mailbox/plugin/deleted-messages-vault-blobstore/src/main/java/org/apache/james/vault/DeletedMessageWithStorageInformation.java
+++ b/mailbox/plugin/deleted-messages-vault-blobstore/src/main/java/org/apache/james/vault/DeletedMessageWithStorageInformation.java
@@ -24,19 +24,19 @@ import java.util.Objects;
import com.google.common.base.Preconditions;
public class DeletedMessageWithStorageInformation {
- private final DeletedMessage deletedmessage;
+ private final DeletedMessage deletedMessage;
private final StorageInformation storageInformation;
- public DeletedMessageWithStorageInformation(DeletedMessage deletedmessage, StorageInformation storageInformation) {
- Preconditions.checkNotNull(deletedmessage);
+ public DeletedMessageWithStorageInformation(DeletedMessage deletedMessage, StorageInformation storageInformation) {
+ Preconditions.checkNotNull(deletedMessage);
Preconditions.checkNotNull(storageInformation);
- this.deletedmessage = deletedmessage;
+ this.deletedMessage = deletedMessage;
this.storageInformation = storageInformation;
}
- public DeletedMessage getDeletedmessage() {
- return deletedmessage;
+ public DeletedMessage getDeletedMessage() {
+ return deletedMessage;
}
public StorageInformation getStorageInformation() {
@@ -48,7 +48,7 @@ public class DeletedMessageWithStorageInformation {
if (o instanceof DeletedMessageWithStorageInformation) {
DeletedMessageWithStorageInformation that = (DeletedMessageWithStorageInformation) o;
- return Objects.equals(this.deletedmessage, that.deletedmessage)
+ return Objects.equals(this.deletedMessage, that.deletedMessage)
&& Objects.equals(this.storageInformation, that.storageInformation);
}
return false;
@@ -56,6 +56,6 @@ public class DeletedMessageWithStorageInformation {
@Override
public final int hashCode() {
- return Objects.hash(deletedmessage, storageInformation);
+ return Objects.hash(deletedMessage, storageInformation);
}
}
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageMetadataVaultContract.java b/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageMetadataVaultContract.java
new file mode 100644
index 0000000..714c911
--- /dev/null
+++ b/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageMetadataVaultContract.java
@@ -0,0 +1,198 @@
+/****************************************************************
+ * 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.vault;
+
+import static org.apache.james.vault.DeletedMessageFixture.USER;
+import static org.apache.james.vault.DeletedMessageVaultMetadataFixture.BLOB_ID_2;
+import static org.apache.james.vault.DeletedMessageVaultMetadataFixture.BUCKET_NAME;
+import static org.apache.james.vault.DeletedMessageVaultMetadataFixture.STORAGE_INFORMATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.james.blob.api.BucketName;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface DeletedMessageMetadataVaultContract {
+ DeletedMessageWithStorageInformation DELETED_MESSAGE = new DeletedMessageWithStorageInformation(DeletedMessageFixture.DELETED_MESSAGE, STORAGE_INFORMATION);
+ DeletedMessageWithStorageInformation DELETED_MESSAGE_2 = new DeletedMessageWithStorageInformation(DeletedMessageFixture.DELETED_MESSAGE_2, STORAGE_INFORMATION);
+ BucketName OTHER_BUCKET_NAME = BucketName.of("other");
+ DeletedMessageWithStorageInformation DELETED_MESSAGE_2_OTHER_BUCKET = new DeletedMessageWithStorageInformation(DeletedMessageFixture.DELETED_MESSAGE_2,
+ new StorageInformation(OTHER_BUCKET_NAME, BLOB_ID_2));
+
+ DeletedMessageMetadataVault metadataVault();
+
+ @Test
+ default void listMessagesShouldBeEmptyWhenNoMessageInserted() {
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ default void listMessagesShouldContainPreviouslyInsertedMessage() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).containsOnly(DELETED_MESSAGE);
+ }
+
+ @Test
+ default void listMessagesShouldContainAllPreviouslyInsertedMessages() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).containsOnly(DELETED_MESSAGE, DELETED_MESSAGE_2);
+ }
+
+ @Test
+ default void listMessagesShouldNotReturnMessagesOfOtherBuckets() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2_OTHER_BUCKET)).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).containsOnly(DELETED_MESSAGE);
+ }
+
+ @Test
+ default void listBucketsShouldBeEmptyWhenNoMessageInserted() {
+ Stream<BucketName> messages = Flux.from(metadataVault().listBuckets()).toStream();
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ default void listBucketsShouldReturnAllUsedBuckets() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2_OTHER_BUCKET)).block();
+
+ Stream<BucketName> messages = Flux.from(metadataVault().listBuckets()).toStream();
+ assertThat(messages).containsOnly(BUCKET_NAME, OTHER_BUCKET_NAME);
+ }
+
+ @Test
+ default void listBucketsShouldNotReturnDuplicates() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Stream<BucketName> messages = Flux.from(metadataVault().listBuckets()).toStream();
+ assertThat(messages).containsExactly(BUCKET_NAME);
+ }
+
+ @Test
+ default void listBucketsShouldStillListNotYetDeletedBuckets() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2_OTHER_BUCKET)).block();
+
+ Mono.from(metadataVault().removeBucket(BUCKET_NAME)).block();
+
+ Stream<BucketName> messages = Flux.from(metadataVault().listBuckets()).toStream();
+ assertThat(messages).containsOnly(OTHER_BUCKET_NAME);
+ }
+
+ @Test
+ default void listBucketsShouldNotReturnDeletedBuckets() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Mono.from(metadataVault().removeBucket(BUCKET_NAME)).block();
+
+ Stream<BucketName> messages = Flux.from(metadataVault().listBuckets()).toStream();
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ default void removeBucketShouldOnlyRemoveEntriesOfTheGivenBucket() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2_OTHER_BUCKET)).block();
+
+ Mono.from(metadataVault().removeBucket(BUCKET_NAME)).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(OTHER_BUCKET_NAME, USER)).toStream();
+ assertThat(messages).containsOnly(DELETED_MESSAGE_2_OTHER_BUCKET);
+ }
+
+ @Test
+ default void removeBucketShouldRemoveAllEntriesOfTheGivenBucket() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Mono.from(metadataVault().removeBucket(BUCKET_NAME)).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ default void listMessagesShouldNotReturnRemovedItems() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Mono.from(metadataVault().remove(BUCKET_NAME, USER, DELETED_MESSAGE.getDeletedMessage().getMessageId())).block();
+
+ Stream<DeletedMessageWithStorageInformation> messages = Flux.from(metadataVault().listMessages(BUCKET_NAME, USER)).toStream();
+ assertThat(messages).containsExactly(DELETED_MESSAGE_2);
+ }
+
+ @Test
+ default void removeShouldNotFailWhenTheMessageDoesNotExist() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ assertThatCode(() -> Mono.from(metadataVault()
+ .remove(BUCKET_NAME, USER, DELETED_MESSAGE.getDeletedMessage().getMessageId()))
+ .block())
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ default void retrieveStorageInformationShouldReturnStoredValue() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE)).block();
+
+ StorageInformation storageInformation = Mono.from(metadataVault()
+ .retrieveStorageInformation(USER, DELETED_MESSAGE.getDeletedMessage().getMessageId()))
+ .block();
+
+ assertThat(storageInformation).isEqualTo(DELETED_MESSAGE.getStorageInformation());
+ }
+
+ @Test
+ default void retrieveStorageInformationShouldReturnEmptyWhenNotStored() {
+ Mono.from(metadataVault().store(DELETED_MESSAGE_2)).block();
+
+ Optional<StorageInformation> storageInformation = Mono.from(metadataVault()
+ .retrieveStorageInformation(USER, DELETED_MESSAGE.getDeletedMessage().getMessageId()))
+ .blockOptional();
+
+ assertThat(storageInformation).isEmpty();
+ }
+
+ @Test
+ default void retrieveStorageInformationShouldReturnEmptyWhenUserVaultIsEmpty() {
+ Optional<StorageInformation> storageInformation = Mono.from(metadataVault()
+ .retrieveStorageInformation(USER, DELETED_MESSAGE.getDeletedMessage().getMessageId()))
+ .blockOptional();
+
+ assertThat(storageInformation).isEmpty();
+ }
+}
diff --git a/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java b/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java
index a7a4db8..8bc4ec4 100644
--- a/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java
+++ b/mailbox/plugin/deleted-messages-vault-blobstore/src/test/java/org/apache/james/vault/DeletedMessageVaultMetadataFixture.java
@@ -25,6 +25,7 @@ import org.apache.james.blob.api.HashBlobId;
public interface DeletedMessageVaultMetadataFixture {
BlobId BLOB_ID = new HashBlobId.Factory().from("05dcb33b-8382-4744-923a-bc593ad84d23");
+ BlobId BLOB_ID_2 = new HashBlobId.Factory().from("05dcb33b-8382-4744-923a-bc593ad84d24");
BucketName BUCKET_NAME = BucketName.of("bucket-2019-06-01");
StorageInformation STORAGE_INFORMATION = new StorageInformation(BUCKET_NAME, BLOB_ID);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org