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

Reply via email to