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 ro...@apache.org on 2019/07/22 12:35:16 UTC

[james-project] 05/08: JAMES-2813 Use JsonGenericSerializer to serialize Tasks

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

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

commit d59992753a7dffaef904f38c6195730f16e70d44
Author: RĂ©mi Kowalski <rk...@linagora.com>
AuthorDate: Tue Jul 16 11:34:01 2019 +0200

    JAMES-2813 Use JsonGenericSerializer to serialize Tasks
---
 .../eventstore/cassandra/JsonEventSerializer.java  |  2 +-
 .../tools/indexer/SingleMailboxReindexingTask.java |  4 +-
 server/task-json/pom.xml                           |  4 ++
 .../james/server/task/json/JsonTaskSerializer.java | 32 ++++-----
 .../james/server/task/json/TaskDeserializer.java   | 78 ----------------------
 .../apache/james/server/task/json/dto/TaskDTO.java | 15 +++++
 .../TaskDTOModule.java}                            | 21 ++----
 .../server/task/json/TaskDeserializerTest.java     | 30 ++++-----
 .../james/server/task/json/TaskSerializerTest.java | 11 +--
 .../apache/james/server/task/json/TestTask.java    | 15 ++---
 .../james/server/task/json/dto/TestTaskDTO.java}   | 37 +++++++++-
 .../server/task/json/dto/TestTaskDTOModules.java}  | 23 ++++---
 .../src/main/java/org/apache/james/task/Task.java  |  6 --
 13 files changed, 115 insertions(+), 163 deletions(-)

diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
index a179a9b..4303ce5 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
+++ b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
@@ -43,7 +43,7 @@ public class JsonEventSerializer {
     private JsonGenericSerializer<Event, EventDTO<Event>> jsonGenericSerializer;
 
     @Inject
-    public JsonEventSerializer(@SuppressWarnings("rawtypes") Set<EventDTOModule> modules) {
+    public JsonEventSerializer(Set<EventDTOModule> modules) {
         jsonGenericSerializer = new JsonGenericSerializer(modules);
     }
     
diff --git a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/SingleMailboxReindexingTask.java b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/SingleMailboxReindexingTask.java
index 73882ea..43d1849 100644
--- a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/SingleMailboxReindexingTask.java
+++ b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/SingleMailboxReindexingTask.java
@@ -25,7 +25,6 @@ import java.util.Optional;
 import javax.inject.Inject;
 
 import org.apache.james.mailbox.model.MailboxId;
-import org.apache.james.server.task.json.TaskDeserializer;
 import org.apache.james.task.Task;
 import org.apache.james.task.TaskExecutionDetails;
 
@@ -49,7 +48,7 @@ public class SingleMailboxReindexingTask implements Task {
         }
     }
 
-    public static class Factory implements TaskDeserializer.Factory {
+    public static class Factory {
 
         private MailboxId.Factory mailboxIdFactory;
 
@@ -58,7 +57,6 @@ public class SingleMailboxReindexingTask implements Task {
             this.mailboxIdFactory = mailboxIdFactory;
         }
 
-        @Override
         public Task create(JsonNode parameters) {
             MailboxId mailboxId = mailboxIdFactory.fromString(parameters.get("mailboxId").asText());
             return new SingleMailboxReindexingTask(null, mailboxId);
diff --git a/server/task-json/pom.xml b/server/task-json/pom.xml
index 443edcb..8e91a57 100644
--- a/server/task-json/pom.xml
+++ b/server/task-json/pom.xml
@@ -45,6 +45,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-json</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-task</artifactId>
         </dependency>
         <dependency>
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java b/server/task-json/src/main/java/org/apache/james/server/task/json/JsonTaskSerializer.java
similarity index 64%
copy from event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
copy to server/task-json/src/main/java/org/apache/james/server/task/json/JsonTaskSerializer.java
index a179a9b..a502ce7 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/JsonTaskSerializer.java
@@ -17,53 +17,53 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra;
+package org.apache.james.server.task.json;
 
 import java.io.IOException;
 import java.util.Set;
 
 import javax.inject.Inject;
 
-import org.apache.james.eventsourcing.Event;
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
 import org.apache.james.json.JsonGenericSerializer;
+import org.apache.james.server.task.json.dto.TaskDTO;
+import org.apache.james.server.task.json.dto.TaskDTOModule;
+import org.apache.james.task.Task;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.ImmutableSet;
 
-public class JsonEventSerializer {
+public class JsonTaskSerializer {
 
-    public static class UnknownEventException extends RuntimeException {
-        public UnknownEventException(JsonGenericSerializer.UnknownTypeException original) {
+    public static class UnknownTaskException extends RuntimeException {
+        public UnknownTaskException(JsonGenericSerializer.UnknownTypeException original) {
             super(original);
         }
     }
 
-    private JsonGenericSerializer<Event, EventDTO<Event>> jsonGenericSerializer;
+    private JsonGenericSerializer<Task, TaskDTO<Task>> jsonGenericSerializer;
 
     @Inject
-    public JsonEventSerializer(@SuppressWarnings("rawtypes") Set<EventDTOModule> modules) {
+    public JsonTaskSerializer(@SuppressWarnings("rawtypes") Set<TaskDTOModule> modules) {
         jsonGenericSerializer = new JsonGenericSerializer(modules);
     }
-    
-    public JsonEventSerializer(@SuppressWarnings("rawtypes") EventDTOModule... modules) {
+
+    public JsonTaskSerializer(@SuppressWarnings("rawtypes") TaskDTOModule... modules) {
         this(ImmutableSet.copyOf(modules));
     }
 
-    public String serialize(Event event) throws JsonProcessingException {
+    public String serialize(Task task) throws JsonProcessingException {
         try {
-            return jsonGenericSerializer.serialize(event);
+            return jsonGenericSerializer.serialize(task);
         } catch (JsonGenericSerializer.UnknownTypeException e) {
-            throw new UnknownEventException(e);
+            throw new UnknownTaskException(e);
         }
     }
 
-    public Event deserialize(String value) throws IOException {
+    public Task deserialize(String value) throws IOException {
         try {
             return jsonGenericSerializer.deserialize(value);
         } catch (JsonGenericSerializer.UnknownTypeException e) {
-            throw new UnknownEventException(e);
+            throw new UnknownTaskException(e);
         }
     }
 
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java b/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java
deleted file mode 100644
index 335a2d1..0000000
--- a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.apache.james.server.task.json;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
-import org.apache.james.task.Task;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.MoreObjects;
-
-public class TaskDeserializer {
-
-    private final ObjectMapper objectMapper;
-
-    public interface Factory {
-        Task create(JsonNode parameters);
-    }
-
-    public static class Type {
-        public static Type of(String typeName) {
-            return new Type(typeName);
-        }
-
-        private final String typeName;
-
-        private Type(String typeName) {
-            this.typeName = typeName;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof Type) {
-                Type that = (Type) o;
-
-                return Objects.equals(this.typeName, that.typeName);
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(typeName);
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .add("typeName", typeName)
-                    .toString();
-        }
-    }
-
-    private final Map<Type, Factory> registry;
-
-    public TaskDeserializer(Map<Type, Factory> registry) {
-        this.registry = registry;
-        objectMapper = new ObjectMapper();
-    }
-
-    public Task deserialize(String taskAsString) throws IOException {
-        JsonNode taskAsJson = objectMapper.readTree(taskAsString);
-        JsonNode parameters = taskAsJson.get("parameters");
-
-        return getFactory(taskAsJson).create(parameters);
-    }
-
-    private Factory getFactory(JsonNode taskAsJson) {
-        Type type = Optional.ofNullable(taskAsJson.get("type"))
-            .map(JsonNode::asText)
-            .map(Type::of)
-            .orElseThrow(() -> new InvalidTaskTypeException());
-        return Optional.ofNullable(registry.get(type))
-            .orElseThrow(() -> new UnsupportedTypeException(type));
-    }
-}
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTO.java b/server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTO.java
new file mode 100644
index 0000000..3dc3670
--- /dev/null
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTO.java
@@ -0,0 +1,15 @@
+package org.apache.james.server.task.json.dto;
+
+import org.apache.james.json.DTO;
+import org.apache.james.task.Task;
+
+public interface TaskDTO<T extends Task> extends DTO<T> {
+
+    T toTask();
+
+    String getType();
+
+    default T toDomainObject() {
+        return toTask();
+    }
+}
\ No newline at end of file
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java b/server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTOModule.java
similarity index 68%
rename from server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java
rename to server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTOModule.java
index ea39a08..0737025 100644
--- a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/dto/TaskDTOModule.java
@@ -16,25 +16,18 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  ****************************************************************/
-package org.apache.james.server.task.json;
+package org.apache.james.server.task.json.dto;
 
+import org.apache.james.json.DTOModule;
 import org.apache.james.task.Task;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+public class TaskDTOModule<T extends Task, U extends TaskDTO<T>> extends DTOModule<T, U> {
 
-public class TaskSerializer {
-
-    private ObjectMapper objectMapper;
-
-    public TaskSerializer() {
-        this.objectMapper = new ObjectMapper();
+    public static <TaskTypeT extends Task> Builder<TaskTypeT> forTask(Class<TaskTypeT> taskType) {
+        return new Builder<>(taskType);
     }
 
-    public JsonNode serialize(Task task) {
-        return JsonNodeFactory.instance.objectNode()
-            .put("type", task.type())
-            .set("parameters", objectMapper.valueToTree(task.parameters()));
+    public TaskDTOModule(DTOConverter<T, U> converter, Class<T> domainObjectType, Class<U> dtoType, String typeName) {
+        super(converter, domainObjectType, dtoType, typeName);
     }
 }
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java
index 2698b69..13f4062 100644
--- a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java
@@ -20,69 +20,65 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.io.IOException;
 
+import org.apache.james.server.task.json.dto.TestTaskDTOModules;
 import org.apache.james.task.Task;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
-import com.google.common.collect.ImmutableMap;
-
 class TaskDeserializerTest {
 
     private static final String TASK_AS_STRING = "{" +
         "\"type\": \"testTask\"," +
-        "\"parameters\": {\"parameter\": \"1\"}" +
+        "\"parameter\": 1" +
         "}";
 
     private static final String UNREGISTERED_TASK_AS_STRING = "{" +
         "\"type\": \"unknown\"," +
-        "\"parameters\": {\"parameter\": \"1\"}" +
+        "\"parameter\": 1" +
         "}";
 
     private static final String TWO_TYPES_TASK_AS_STRING = "{" +
         "\"type\": \"testTask\"," +
         "\"type\": \"unknown\"," +
-        "\"parameters\": {\"parameter\": \"1\"}" +
+        "\"parameter\": 1" +
         "}";
 
     private static final String MISSING_TASK_AS_STRING = "{" +
-        "\"parameters\": {\"parameter\": \"1\"}" +
+        "\"parameter\": 1" +
         "}";
 
-    private TaskDeserializer testee;
+    private JsonTaskSerializer testee;
 
     @BeforeEach
     void setUp() {
-        TestTask.Factory factory = new TestTask.Factory();
-        ImmutableMap<TaskDeserializer.Type, TaskDeserializer.Factory> registry = ImmutableMap.of(TaskDeserializer.Type.of("testTask"), factory);
-        testee = new TaskDeserializer(registry);
+        testee = new JsonTaskSerializer(TestTaskDTOModules.TEST_TYPE);
     }
 
     @Test
     void shouldDeserializeTaskWithRegisteredType() throws IOException {
         Task task = testee.deserialize(TASK_AS_STRING);
         Assertions.assertThat(task).isInstanceOf(TestTask.class);
-        Assertions.assertThat(task.parameters()).isEqualTo(ImmutableMap.of("parameter", "1"));
+        TestTask testTask = (TestTask) task;
+        Assertions.assertThat(testTask.getParameter()).isEqualTo(1L);
     }
 
     @Test
     void shouldThrowWhenNotRegisteredType() {
         assertThatThrownBy(() -> testee.deserialize(UNREGISTERED_TASK_AS_STRING))
-            .isInstanceOf(UnsupportedTypeException.class);
+            .isInstanceOf(JsonTaskSerializer.UnknownTaskException.class);
     }
 
     @Test
     void shouldThrowWhenMissingType() {
         assertThatThrownBy(() -> testee.deserialize(MISSING_TASK_AS_STRING))
-            .isInstanceOf(InvalidTaskTypeException.class);
+            .isInstanceOf(JsonTaskSerializer.UnknownTaskException.class);
     }
 
-    @Disabled("Not supported yet, fixed later")
     @Test
     void shouldThrowWhenDuplicateType() {
         assertThatThrownBy(() -> testee.deserialize(TWO_TYPES_TASK_AS_STRING))
-            .isInstanceOf(InvalidTaskTypeException.class);
+            .isInstanceOf(JsonTaskSerializer.UnknownTaskException.class);
     }
 
-}
\ No newline at end of file
+}
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java
index 12a3758..fb6f1ef 100644
--- a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java
@@ -16,6 +16,7 @@
  * ***************************************************************/
 package org.apache.james.server.task.json;
 
+import org.apache.james.server.task.json.dto.TestTaskDTOModules;
 import org.junit.jupiter.api.Test;
 
 import net.javacrumbs.jsonunit.assertj.JsonAssertions;
@@ -24,14 +25,14 @@ class TaskSerializerTest {
 
     private static final String TASK_AS_STRING = "{" +
         "\"type\": \"testTask\"," +
-        "\"parameters\": {\"parameter\": \"1\"}" +
+        "\"parameter\": 1" +
         "}";
 
     @Test
-    void shouldSerializeTaskWithItsType() {
-        TaskSerializer testee = new TaskSerializer();
+    void shouldSerializeTaskWithItsType() throws Exception {
+        JsonTaskSerializer testee = new JsonTaskSerializer(TestTaskDTOModules.TEST_TYPE);
         long parameter = 1L;
         TestTask task = new TestTask(parameter);
-        JsonAssertions.assertThatJson(testee.serialize(task).toString()).isEqualTo(TASK_AS_STRING);
+        JsonAssertions.assertThatJson(testee.serialize(task)).isEqualTo(TASK_AS_STRING);
     }
-}
\ No newline at end of file
+}
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java
index 4ea611f..84aa4a4 100644
--- a/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java
@@ -38,27 +38,20 @@ public class TestTask implements Task {
         return null;
     }
 
+    public long getParameter() {
+        return parameter;
+    }
+
     @Override
     public String type() {
         return "testTask";
     }
 
-    @Override
-    public Map<String, String> parameters() {
-        return ImmutableMap.of("parameter", String.valueOf(parameter));
-    }
 
     @Override
     public Optional<TaskExecutionDetails.AdditionalInformation> details() {
         return Optional.empty();
     }
 
-    public static class Factory implements TaskDeserializer.Factory {
 
-        @Override
-        public Task create(JsonNode parameters) {
-            long parameter = parameters.get("parameter").asLong();
-            return new TestTask(parameter);
-        }
-    }
 }
\ No newline at end of file
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java b/server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTO.java
similarity index 59%
rename from server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java
rename to server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTO.java
index 8382937..ae8928f 100644
--- a/server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTO.java
@@ -16,7 +16,40 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  ****************************************************************/
-package org.apache.james.server.task.json;
 
-public class InvalidTaskTypeException extends RuntimeException {
+package org.apache.james.server.task.json.dto;
+
+import org.apache.james.server.task.json.TestTask;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class TestTaskDTO implements TaskDTO<TestTask> {
+    private final long parameter;
+    private final String type;
+
+
+    @JsonCreator
+    public TestTaskDTO(@JsonProperty("type") String type, @JsonProperty("parameter") long parameter) {
+        this.type = type;
+        this.parameter = parameter;
+    }
+
+    public long getParameter() {
+        return parameter;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @JsonIgnore
+    @Override
+    public TestTask toTask() {
+        return new TestTask(parameter);
+    }
+
+
 }
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java b/server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTOModules.java
similarity index 73%
rename from server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java
rename to server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTOModules.java
index 498ee8c..499b96c 100644
--- a/server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/dto/TestTaskDTOModules.java
@@ -16,17 +16,20 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  ****************************************************************/
-package org.apache.james.server.task.json;
 
-public class UnsupportedTypeException extends RuntimeException {
-    private final TaskDeserializer.Type type;
+package org.apache.james.server.task.json.dto;
 
-    public UnsupportedTypeException(TaskDeserializer.Type type) {
-        this.type = type;
-    }
+import org.apache.james.server.task.json.TestTask;
+
+public interface TestTaskDTOModules {
+
+    TaskDTOModule TEST_TYPE = TaskDTOModule
+        .forTask(TestTask.class)
+        .convertToDTO(TestTaskDTO.class)
+        .convertWith((task, typeName) -> new TestTaskDTO(
+            typeName,
+            task.getParameter()))
+        .typeName("testTask")
+        .withFactory(TaskDTOModule::new);
 
-    @Override
-    public String getMessage() {
-        return "The type " + type + " is not registered when trying to deserialize it";
-    }
 }
diff --git a/server/task/src/main/java/org/apache/james/task/Task.java b/server/task/src/main/java/org/apache/james/task/Task.java
index 3a3368d..2edb39d 100644
--- a/server/task/src/main/java/org/apache/james/task/Task.java
+++ b/server/task/src/main/java/org/apache/james/task/Task.java
@@ -20,11 +20,9 @@
 package org.apache.james.task;
 
 import java.util.Arrays;
-import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-import org.apache.commons.lang3.NotImplementedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -99,10 +97,6 @@ public interface Task {
         return UNKNOWN;
     }
 
-    default Map<String, String> parameters() {
-        throw new NotImplementedException("Tasks should implement 'parameters' to be serializable");
-    }
-
     default Optional<TaskExecutionDetails.AdditionalInformation> details() {
         return Optional.empty();
     }


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