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 <btell...@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-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org