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 2018/08/30 13:15:58 UTC

[16/26] james-project git commit: JAMES-2529 Extract ContentMatcher and HeaderExtractor from MailMatcher

JAMES-2529 Extract ContentMatcher and HeaderExtractor from MailMatcher


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/7527daec
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/7527daec
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/7527daec

Branch: refs/heads/master
Commit: 7527daec794b8e0022161e079e9a2bf710c58e77
Parents: aee5424
Author: Benoit Tellier <bt...@linagora.com>
Authored: Thu Aug 30 15:02:55 2018 +0700
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Thu Aug 30 15:07:03 2018 +0200

----------------------------------------------------------------------
 .../jmap/mailet/filter/ContentMatcher.java      | 118 +++++++++++++++
 .../jmap/mailet/filter/HeaderExtractor.java     |  93 ++++++++++++
 .../james/jmap/mailet/filter/MailMatcher.java   | 150 -------------------
 3 files changed, 211 insertions(+), 150 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
new file mode 100644
index 0000000..a388332
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java
@@ -0,0 +1,118 @@
+/****************************************************************
+ * 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.mailet.filter;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+public interface ContentMatcher {
+
+    class AddressHeader {
+        private static final Logger LOGGER = LoggerFactory.getLogger(AddressHeader.class);
+
+        private final Optional<String> personal;
+        private final Optional<String> address;
+        private final String fullAddress;
+
+        private AddressHeader(String fullAddress) {
+            this.fullAddress = fullAddress;
+            Optional<InternetAddress> internetAddress = parseFullAddress();
+            this.personal = internetAddress.map(InternetAddress::getPersonal);
+            this.address = internetAddress.map(InternetAddress::getAddress);
+        }
+
+        private Optional<InternetAddress> parseFullAddress() {
+            try {
+                return Optional.of(new InternetAddress(fullAddress));
+            } catch (AddressException e) {
+                LOGGER.error("error while parsing full address {}", fullAddress, e);
+                return Optional.empty();
+            }
+        }
+    }
+
+    ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch));
+    ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER);
+    ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch));
+    ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER);
+
+    ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> contents
+        .map(ContentMatcher::asAddressHeader)
+        .anyMatch(addressHeader -> StringUtils.containsIgnoreCase(addressHeader.fullAddress, valueToMatch));
+    ContentMatcher ADDRESS_NOT_CONTAINS_MATCHER = negate(ADDRESS_CONTAINS_MATCHER);
+    ContentMatcher ADDRESS_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents
+        .map(ContentMatcher::asAddressHeader)
+        .anyMatch(addressHeader ->
+            valueToMatch.equalsIgnoreCase(addressHeader.fullAddress)
+                || addressHeader.address.map(valueToMatch::equalsIgnoreCase).orElse(false)
+                || addressHeader.personal.map(valueToMatch::equalsIgnoreCase).orElse(false));
+    ContentMatcher ADDRESS_NOT_EXACTLY_EQUALS_MATCHER = negate(ADDRESS_EXACTLY_EQUALS_MATCHER);
+
+    Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder()
+        .put(Rule.Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER)
+        .put(Rule.Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER)
+        .put(Rule.Condition.Comparator.EXACTLY_EQUALS, ADDRESS_EXACTLY_EQUALS_MATCHER)
+        .put(Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, ADDRESS_NOT_EXACTLY_EQUALS_MATCHER)
+        .build();
+
+    Map<Rule.Condition.Comparator, ContentMatcher> CONTENT_STRING_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder()
+        .put(Rule.Condition.Comparator.CONTAINS, STRING_CONTAINS_MATCHER)
+        .put(Rule.Condition.Comparator.NOT_CONTAINS, STRING_NOT_CONTAINS_MATCHER)
+        .put(Rule.Condition.Comparator.EXACTLY_EQUALS, STRING_EXACTLY_EQUALS_MATCHER)
+        .put(Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, STRING_NOT_EXACTLY_EQUALS_MATCHER)
+        .build();
+
+    Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>>builder()
+        .put(Rule.Condition.Field.SUBJECT, CONTENT_STRING_MATCHER_REGISTRY)
+        .put(Rule.Condition.Field.TO, HEADER_ADDRESS_MATCHER_REGISTRY)
+        .put(Rule.Condition.Field.CC, HEADER_ADDRESS_MATCHER_REGISTRY)
+        .put(Rule.Condition.Field.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY)
+        .put(Rule.Condition.Field.FROM, HEADER_ADDRESS_MATCHER_REGISTRY)
+        .build();
+
+    static ContentMatcher negate(ContentMatcher contentMatcher) {
+        return (Stream<String> contents, String valueToMatch) ->
+            !contentMatcher.match(contents, valueToMatch);
+    }
+
+    static Optional<ContentMatcher> asContentMatcher(Rule.Condition.Field field, Rule.Condition.Comparator comparator) {
+        return Optional
+            .ofNullable(CONTENT_MATCHER_REGISTRY.get(field))
+            .map(matcherRegistry -> matcherRegistry.get(comparator));
+    }
+
+    static AddressHeader asAddressHeader(String addressAsString) {
+        return new AddressHeader(addressAsString);
+    }
+
+    boolean match(Stream<String> contents, String valueToMatch);
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java
new file mode 100644
index 0000000..65d1629
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java
@@ -0,0 +1,93 @@
+/****************************************************************
+ * 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.mailet.filter;
+
+import static org.apache.mailet.base.RFC2822Headers.FROM;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import javax.mail.Address;
+import javax.mail.Message;
+
+import org.apache.james.javax.AddressHelper;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.mime4j.util.MimeUtil;
+import org.apache.james.util.StreamUtils;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.fge.lambdas.functions.ThrowingFunction;
+import com.google.common.collect.ImmutableMap;
+
+public interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> {
+    Logger LOGGER = LoggerFactory.getLogger(HeaderExtractor.class);
+
+    HeaderExtractor SUBJECT_EXTRACTOR = mail ->
+        StreamUtils.ofNullables(mail.getMessage().getSubject());
+    HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC);
+    HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO);
+    HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR);
+    HeaderExtractor FROM_EXTRACTOR = addressExtractor(mail -> mail.getMessage().getFrom(), FROM);
+
+    Map<Rule.Condition.Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Rule.Condition.Field, HeaderExtractor>builder()
+        .put(Rule.Condition.Field.SUBJECT, SUBJECT_EXTRACTOR)
+        .put(Rule.Condition.Field.RECIPIENT, RECIPIENT_EXTRACTOR)
+        .put(Rule.Condition.Field.FROM, FROM_EXTRACTOR)
+        .put(Rule.Condition.Field.CC, CC_EXTRACTOR)
+        .put(Rule.Condition.Field.TO, TO_EXTRACTOR)
+        .build();
+
+    static HeaderExtractor and(HeaderExtractor headerExtractor1, HeaderExtractor headerExtractor2) {
+        return (Mail mail) -> StreamUtils.flatten(headerExtractor1.apply(mail), headerExtractor2.apply(mail));
+    }
+
+    static HeaderExtractor recipientExtractor(Message.RecipientType type) {
+        ThrowingFunction<Mail, Address[]> addressGetter = mail -> mail.getMessage().getRecipients(type);
+        String fallbackHeaderName = type.toString();
+
+        return addressExtractor(addressGetter, fallbackHeaderName);
+    }
+
+    static HeaderExtractor addressExtractor(ThrowingFunction<Mail, Address[]> addressGetter, String fallbackHeaderName) {
+        return mail -> {
+            try {
+                return toContent(addressGetter.apply(mail));
+            } catch (Exception e) {
+                LOGGER.info("Failed parsing header. Falling back to unparsed header value matching", e);
+                return Stream.of(mail.getMessage().getHeader(fallbackHeaderName))
+                    .map(MimeUtil::unscrambleHeaderValue);
+            }
+        };
+    }
+
+    static Stream<String> toContent(Address[] addresses) {
+        return Optional.ofNullable(addresses)
+            .map(AddressHelper::asStringStream)
+            .orElse(Stream.empty());
+    }
+
+    static Optional<HeaderExtractor> asHeaderExtractor(Rule.Condition.Field field) {
+        return Optional.ofNullable(
+            HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field));
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java
index 031e696..ed5db25 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java
@@ -20,169 +20,19 @@
 package org.apache.james.jmap.mailet.filter;
 
 import static org.apache.james.jmap.api.filtering.Rule.Condition;
-import static org.apache.mailet.base.RFC2822Headers.FROM;
 
-import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.internet.AddressException;
-import javax.mail.internet.InternetAddress;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.james.javax.AddressHelper;
 import org.apache.james.jmap.api.filtering.Rule;
-import org.apache.james.jmap.api.filtering.Rule.Condition.Field;
-import org.apache.james.mime4j.util.MimeUtil;
-import org.apache.james.util.StreamUtils;
 import org.apache.mailet.Mail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.github.fge.lambdas.functions.ThrowingFunction;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
 
 public interface MailMatcher {
 
-    interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> {
-        Logger LOGGER = LoggerFactory.getLogger(HeaderExtractor.class);
-
-        HeaderExtractor SUBJECT_EXTRACTOR = mail ->
-            StreamUtils.ofNullables(mail.getMessage().getSubject());
-        HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC);
-        HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO);
-        HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR);
-        HeaderExtractor FROM_EXTRACTOR = addressExtractor(mail -> mail.getMessage().getFrom(), FROM);
-
-        Map<Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Field, HeaderExtractor>builder()
-            .put(Field.SUBJECT, SUBJECT_EXTRACTOR)
-            .put(Field.RECIPIENT, RECIPIENT_EXTRACTOR)
-            .put(Field.FROM, FROM_EXTRACTOR)
-            .put(Field.CC, CC_EXTRACTOR)
-            .put(Field.TO, TO_EXTRACTOR)
-            .build();
-
-        static HeaderExtractor and(HeaderExtractor headerExtractor1, HeaderExtractor headerExtractor2) {
-            return (Mail mail) -> StreamUtils.flatten(headerExtractor1.apply(mail), headerExtractor2.apply(mail));
-        }
-
-        static HeaderExtractor recipientExtractor(Message.RecipientType type) {
-            ThrowingFunction<Mail, Address[]> addressGetter = mail -> mail.getMessage().getRecipients(type);
-            String fallbackHeaderName = type.toString();
-
-            return addressExtractor(addressGetter, fallbackHeaderName);
-        }
-
-        static HeaderExtractor addressExtractor(ThrowingFunction<Mail, Address[]> addressGetter, String fallbackHeaderName) {
-            return mail -> {
-                try {
-                    return toContent(addressGetter.apply(mail));
-                } catch (Exception e) {
-                    LOGGER.info("Failed parsing header. Falling back to unparsed header value matching", e);
-                    return Stream.of(mail.getMessage().getHeader(fallbackHeaderName))
-                        .map(MimeUtil::unscrambleHeaderValue);
-                }
-            };
-        }
-
-        static Stream<String> toContent(Address[] addresses) {
-            return Optional.ofNullable(addresses)
-                .map(AddressHelper::asStringStream)
-                .orElse(Stream.empty());
-        }
-
-        static Optional<HeaderExtractor> asHeaderExtractor(Field field) {
-            return Optional.ofNullable(
-                HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field));
-        }
-    }
-
-    interface ContentMatcher {
-
-        class AddressHeader {
-            private static final Logger LOGGER = LoggerFactory.getLogger(AddressHeader.class);
-
-            private final Optional<String> personal;
-            private final Optional<String> address;
-            private final String fullAddress;
-
-            private AddressHeader(String fullAddress) {
-                this.fullAddress = fullAddress;
-                Optional<InternetAddress> internetAddress = parseFullAddress();
-                this.personal = internetAddress.map(InternetAddress::getPersonal);
-                this.address = internetAddress.map(InternetAddress::getAddress);
-            }
-
-            private Optional<InternetAddress> parseFullAddress() {
-                try {
-                    return Optional.of(new InternetAddress(fullAddress));
-                } catch (AddressException e) {
-                    LOGGER.error("error while parsing full address {}", fullAddress, e);
-                    return Optional.empty();
-                }
-            }
-        }
-
-        ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch));
-        ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER);
-        ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch));
-        ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER);
-
-        ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> contents
-            .map(ContentMatcher::asAddressHeader)
-            .anyMatch(addressHeader -> StringUtils.containsIgnoreCase(addressHeader.fullAddress, valueToMatch));
-        ContentMatcher ADDRESS_NOT_CONTAINS_MATCHER = negate(ADDRESS_CONTAINS_MATCHER);
-        ContentMatcher ADDRESS_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents
-            .map(ContentMatcher::asAddressHeader)
-            .anyMatch(addressHeader ->
-                valueToMatch.equalsIgnoreCase(addressHeader.fullAddress)
-                    || addressHeader.address.map(valueToMatch::equalsIgnoreCase).orElse(false)
-                    || addressHeader.personal.map(valueToMatch::equalsIgnoreCase).orElse(false));
-        ContentMatcher ADDRESS_NOT_EXACTLY_EQUALS_MATCHER = negate(ADDRESS_EXACTLY_EQUALS_MATCHER);
-
-        Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder()
-            .put(Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER)
-            .put(Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER)
-            .put(Condition.Comparator.EXACTLY_EQUALS, ADDRESS_EXACTLY_EQUALS_MATCHER)
-            .put(Condition.Comparator.NOT_EXACTLY_EQUALS, ADDRESS_NOT_EXACTLY_EQUALS_MATCHER)
-            .build();
-
-        Map<Rule.Condition.Comparator, ContentMatcher> CONTENT_STRING_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder()
-            .put(Condition.Comparator.CONTAINS, STRING_CONTAINS_MATCHER)
-            .put(Condition.Comparator.NOT_CONTAINS, STRING_NOT_CONTAINS_MATCHER)
-            .put(Condition.Comparator.EXACTLY_EQUALS, STRING_EXACTLY_EQUALS_MATCHER)
-            .put(Condition.Comparator.NOT_EXACTLY_EQUALS, STRING_NOT_EXACTLY_EQUALS_MATCHER)
-            .build();
-
-        Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>>builder()
-            .put(Condition.Field.SUBJECT, CONTENT_STRING_MATCHER_REGISTRY)
-            .put(Condition.Field.TO, HEADER_ADDRESS_MATCHER_REGISTRY)
-            .put(Condition.Field.CC, HEADER_ADDRESS_MATCHER_REGISTRY)
-            .put(Condition.Field.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY)
-            .put(Condition.Field.FROM, HEADER_ADDRESS_MATCHER_REGISTRY)
-            .build();
-
-        static ContentMatcher negate(ContentMatcher contentMatcher) {
-            return (Stream<String> contents, String valueToMatch) ->
-                !contentMatcher.match(contents, valueToMatch);
-        }
-
-        static Optional<ContentMatcher> asContentMatcher(Condition.Field field, Condition.Comparator comparator) {
-            return Optional
-                .ofNullable(CONTENT_MATCHER_REGISTRY.get(field))
-                .map(matcherRegistry -> matcherRegistry.get(comparator));
-        }
-
-        static AddressHeader asAddressHeader(String addressAsString) {
-            return new AddressHeader(addressAsString);
-        }
-
-        boolean match(Stream<String> contents, String valueToMatch);
-    }
-
     class HeaderMatcher implements MailMatcher {
 
         private static final Logger LOGGER = LoggerFactory.getLogger(HeaderMatcher.class);


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