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 ma...@apache.org on 2018/05/22 12:55:04 UTC

[3/4] james-project git commit: JAMES-2399 Allow specifying per threshold templates

JAMES-2399 Allow specifying per threshold templates


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

Branch: refs/heads/master
Commit: 0dd4b49fe79c5246a0d68a5b579a3f17d93f6df5
Parents: 015d9ae
Author: benwa <bt...@linagora.com>
Authored: Mon May 21 16:33:06 2018 +0700
Committer: Matthieu Baechler <ma...@apache.org>
Committed: Tue May 22 14:54:28 2018 +0200

----------------------------------------------------------------------
 .../QuotaMailingListenerConfiguration.java      | 147 +++++++++++++++----
 .../subscribers/QuotaThresholdNotice.java       |  16 +-
 .../QuotaMailingListenerConfigurationTest.java  | 102 ++++++++++---
 .../subscribers/QuotaThresholdNoticeTest.java   | 135 ++++++++++++++++-
 .../src/test/resources/templates/body1.mustache |   1 +
 .../src/test/resources/templates/body2.mustache |   1 +
 .../test/resources/templates/subject1.mustache  |   1 +
 .../test/resources/templates/subject2.mustache  |   1 +
 .../src/test/resources/listeners.xml            |   8 +-
 .../src/test/resources/listeners.xml            |   8 +-
 10 files changed, 363 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
index 6f2e7a6..fce7d35 100644
--- a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
+++ b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfiguration.java
@@ -20,14 +20,17 @@
 package org.apache.james.mailbox.quota.mailing;
 
 import java.time.Duration;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Optional;
 
 import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.mailbox.quota.model.QuotaThreshold;
 import org.apache.james.mailbox.quota.model.QuotaThresholds;
+import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.TimeConverter;
 
 import com.github.steveash.guavate.Guavate;
@@ -35,6 +38,7 @@ import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 public class QuotaMailingListenerConfiguration {
 
@@ -43,7 +47,7 @@ public class QuotaMailingListenerConfiguration {
         String BODY_TEMPLATE = "bodyTemplate";
         String GRACE_PERIOD = "gracePeriod";
         String THRESHOLDS = "thresholds.threshold";
-        String ROOT_KEY = "";
+        String THRESHOLD_VALUE = "value";
         String NAME = "name";
     }
 
@@ -75,16 +79,76 @@ public class QuotaMailingListenerConfiguration {
             .map(Duration::ofMillis);
     }
 
-    private static ImmutableList<QuotaThreshold> readThresholds(HierarchicalConfiguration config) {
+    private static ImmutableMap<QuotaThreshold, RenderingInformation> readThresholds(HierarchicalConfiguration config) {
         return config.configurationsAt(XmlKeys.THRESHOLDS)
             .stream()
-            .map(node -> node.getDouble(XmlKeys.ROOT_KEY))
-            .map(QuotaThreshold::new)
-            .collect(Guavate.toImmutableList());
+            .map(node -> Pair.of(
+                node.getDouble(XmlKeys.THRESHOLD_VALUE),
+                RenderingInformation.from(
+                    Optional.ofNullable(node.getString(XmlKeys.BODY_TEMPLATE)),
+                    Optional.ofNullable(node.getString(XmlKeys.SUBJECT_TEMPLATE)))))
+            .collect(Guavate.toImmutableMap(
+                pair -> new QuotaThreshold(pair.getLeft()),
+                Pair::getRight));
+    }
+
+    public static class RenderingInformation {
+        private final Optional<String> bodyTemplate;
+        private final Optional<String> subjectTemplate;
+
+        public static RenderingInformation from(Optional<String> bodyTemplate, Optional<String> subjectTemplate) {
+            return new RenderingInformation(
+                bodyTemplate,
+                subjectTemplate);
+        }
+
+        public static RenderingInformation from(String bodyTemplate, String subjectTemplate) {
+            return from(Optional.of(bodyTemplate), Optional.of(subjectTemplate));
+        }
+
+        private RenderingInformation(Optional<String> bodyTemplate, Optional<String> subjectTemplate) {
+            Preconditions.checkArgument(!bodyTemplate.equals(Optional.of("")), "Pass a non empty bodyTemplate");
+            Preconditions.checkArgument(!subjectTemplate.equals(Optional.of("")), "Pass a non empty subjectTemplate");
+            this.bodyTemplate = bodyTemplate;
+            this.subjectTemplate = subjectTemplate;
+        }
+
+        public Optional<String> getBodyTemplate() {
+            return bodyTemplate;
+        }
+
+        public Optional<String> getSubjectTemplate() {
+            return subjectTemplate;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof RenderingInformation) {
+                RenderingInformation that = (RenderingInformation) o;
+
+                return Objects.equals(this.bodyTemplate, that.bodyTemplate)
+                    && Objects.equals(this.subjectTemplate, that.subjectTemplate);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(bodyTemplate, subjectTemplate);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("bodyTemplate", bodyTemplate)
+                .add("subjectTemplate", subjectTemplate)
+                .toString();
+        }
     }
 
     public static class Builder {
         private ImmutableList.Builder<QuotaThreshold> thresholds;
+        private ImmutableMap.Builder<QuotaThreshold, RenderingInformation> toRenderingInformation;
         private Optional<Duration> gradePeriod;
         private Optional<String> bodyTemplate;
         private Optional<String> subjectTemplate;
@@ -92,24 +156,37 @@ public class QuotaMailingListenerConfiguration {
 
         private Builder() {
             thresholds = ImmutableList.builder();
+            toRenderingInformation = ImmutableMap.builder();
             gradePeriod = Optional.empty();
             bodyTemplate = Optional.empty();
             subjectTemplate = Optional.empty();
             name = Optional.empty();
         }
 
+        public Builder addThreshold(QuotaThreshold quotaThreshold, RenderingInformation renderingInformation) {
+            thresholds.add(quotaThreshold);
+            toRenderingInformation.put(quotaThreshold, renderingInformation);
+            return this;
+        }
+
         public Builder addThreshold(QuotaThreshold quotaThreshold) {
             thresholds.add(quotaThreshold);
             return this;
         }
 
         public Builder addThresholds(QuotaThreshold... quotaThresholds) {
-            thresholds.add(quotaThresholds);
+            Arrays.stream(quotaThresholds)
+                .forEach(this::addThreshold);
             return this;
         }
 
         public Builder addThresholds(Collection<QuotaThreshold> quotaThresholds) {
-            thresholds.addAll(quotaThresholds);
+            quotaThresholds.forEach(this::addThreshold);
+            return this;
+        }
+
+        public Builder addThresholds(ImmutableMap<QuotaThreshold, RenderingInformation> quotaThresholds) {
+            quotaThresholds.forEach(this::addThreshold);
             return this;
         }
 
@@ -130,11 +207,6 @@ public class QuotaMailingListenerConfiguration {
             return this;
         }
 
-        public Builder gracePeriod(Optional<Duration> duration) {
-            duration.ifPresent(this::gracePeriod);
-            return this;
-        }
-
         public Builder bodyTemplate(Optional<String> bodyTemplate) {
             bodyTemplate.ifPresent(this::bodyTemplate);
             return this;
@@ -145,6 +217,11 @@ public class QuotaMailingListenerConfiguration {
             return this;
         }
 
+        public Builder gracePeriod(Optional<Duration> duration) {
+            duration.ifPresent(this::gracePeriod);
+            return this;
+        }
+
         public Builder name(String name) {
             Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "Pass a non null/empty name");
             this.name = Optional.of(name);
@@ -158,16 +235,18 @@ public class QuotaMailingListenerConfiguration {
 
         public QuotaMailingListenerConfiguration build() {
             return new QuotaMailingListenerConfiguration(
+                toRenderingInformation.build(),
                 new QuotaThresholds(thresholds.build()),
                 gradePeriod.orElse(DEFAULT_GRACE_PERIOD),
-                bodyTemplate.orElse(DEFAULT_BODY_TEMPLATE),
-                subjectTemplate.orElse(DEFAULT_SUBJECT_TEMPLATE),
+                bodyTemplate,
+                subjectTemplate,
                 name.orElse(DEFAULT_NAME));
         }
     }
 
     public static final String DEFAULT_BODY_TEMPLATE = FileSystem.CLASSPATH_PROTOCOL + "//templates/QuotaThresholdMailBody.mustache";
     public static final String DEFAULT_SUBJECT_TEMPLATE = FileSystem.CLASSPATH_PROTOCOL + "//templates/QuotaThresholdMailSubject.mustache";
+    public static final RenderingInformation DEFAULT_RENDERING_INFORMATION = RenderingInformation.from(Optional.empty(), Optional.empty());
     public static final Duration DEFAULT_GRACE_PERIOD = Duration.ofDays(1);
     private static final String DEFAULT_NAME = "default";
 
@@ -179,13 +258,17 @@ public class QuotaMailingListenerConfiguration {
         return builder().build();
     }
 
+
+    private final ImmutableMap<QuotaThreshold, RenderingInformation> toRenderingInformation;
     private final QuotaThresholds thresholds;
     private final Duration gracePeriod;
-    private final String bodyTemplate;
-    private final String subjectTemplate;
+    private final Optional<String> bodyTemplate;
+    private final Optional<String> subjectTemplate;
     private final String name;
 
-    private QuotaMailingListenerConfiguration(QuotaThresholds thresholds, Duration gracePeriod, String bodyTemplate, String subjectTemplate, String name) {
+    private QuotaMailingListenerConfiguration(ImmutableMap<QuotaThreshold, RenderingInformation> toRenderingInformation,
+                                              QuotaThresholds thresholds, Duration gracePeriod, Optional<String> bodyTemplate, Optional<String> subjectTemplate, String name) {
+        this.toRenderingInformation = toRenderingInformation;
         this.thresholds = thresholds;
         this.gracePeriod = gracePeriod;
         this.bodyTemplate = bodyTemplate;
@@ -201,12 +284,22 @@ public class QuotaMailingListenerConfiguration {
         return gracePeriod;
     }
 
-    public String getBodyTemplate() {
-        return bodyTemplate;
+    public String getBodyTemplate(QuotaThreshold quotaThreshold) {
+        return OptionalUtils.or(
+            Optional.ofNullable(
+                toRenderingInformation.get(quotaThreshold))
+                    .flatMap(RenderingInformation::getBodyTemplate),
+                bodyTemplate)
+            .orElse(DEFAULT_BODY_TEMPLATE);
     }
 
-    public String getSubjectTemplate() {
-        return subjectTemplate;
+    public String getSubjectTemplate(QuotaThreshold quotaThreshold) {
+        return OptionalUtils.or(
+            Optional.ofNullable(
+                toRenderingInformation.get(quotaThreshold))
+                    .flatMap(RenderingInformation::getSubjectTemplate),
+                subjectTemplate)
+            .orElse(DEFAULT_SUBJECT_TEMPLATE);
     }
 
     public String getName() {
@@ -218,27 +311,29 @@ public class QuotaMailingListenerConfiguration {
         if (o instanceof QuotaMailingListenerConfiguration) {
             QuotaMailingListenerConfiguration that = (QuotaMailingListenerConfiguration) o;
 
-            return Objects.equals(this.thresholds, that.thresholds)
+            return Objects.equals(this.toRenderingInformation, that.toRenderingInformation)
+                && Objects.equals(this.thresholds, that.thresholds)
                 && Objects.equals(this.gracePeriod, that.gracePeriod)
+                && Objects.equals(this.subjectTemplate, that.subjectTemplate)
                 && Objects.equals(this.bodyTemplate, that.bodyTemplate)
-                && Objects.equals(this.name, that.name)
-                && Objects.equals(this.subjectTemplate, that.subjectTemplate);
+                && Objects.equals(this.name, that.name);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(thresholds, gracePeriod, bodyTemplate, subjectTemplate, name);
+        return Objects.hash(toRenderingInformation, thresholds, subjectTemplate, bodyTemplate, gracePeriod, name);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
+            .add("toRenderingInformation", toRenderingInformation)
             .add("thresholds", thresholds)
-            .add("gracePeriod", gracePeriod)
             .add("bodyTemplate", bodyTemplate)
             .add("subjectTemplate", subjectTemplate)
+            .add("gracePeriod", gracePeriod)
             .add("name", name)
             .toString();
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNotice.java
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNotice.java b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNotice.java
index 8e90a12..1d3fdc8 100644
--- a/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNotice.java
+++ b/mailbox/plugin/quota-mailing/src/main/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNotice.java
@@ -26,9 +26,11 @@ import java.io.OutputStreamWriter;
 import java.io.StringReader;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.james.core.builder.MimeMessageBuilder;
@@ -40,6 +42,7 @@ import org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration;
 import org.apache.james.mailbox.quota.model.HistoryEvolution;
 import org.apache.james.mailbox.quota.model.QuotaThreshold;
 import org.apache.james.mailbox.quota.model.QuotaThresholdChange;
+import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.SizeFormat;
 
 import com.github.mustachejava.DefaultMustacheFactory;
@@ -139,12 +142,21 @@ public class QuotaThresholdNotice {
 
     @VisibleForTesting
     String generateSubject(FileSystem fileSystem) throws IOException {
-        return renderTemplate(fileSystem, configuration.getSubjectTemplate());
+        return renderTemplate(fileSystem,
+            configuration.getSubjectTemplate(mostSignificantThreshold()));
     }
 
     @VisibleForTesting
     String generateReport(FileSystem fileSystem) throws IOException {
-        return renderTemplate(fileSystem, configuration.getBodyTemplate());
+        return renderTemplate(fileSystem,
+            configuration.getBodyTemplate(mostSignificantThreshold()));
+    }
+
+    private QuotaThreshold mostSignificantThreshold() {
+        return Stream.of(countThreshold, sizeThreshold)
+            .flatMap(OptionalUtils::toStream)
+            .min(Comparator.reverseOrder())
+            .get();
     }
 
     private String renderTemplate(FileSystem fileSystem, String template) throws IOException {

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfigurationTest.java
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfigurationTest.java b/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfigurationTest.java
index 1668640..e2c39ea 100644
--- a/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfigurationTest.java
+++ b/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/QuotaMailingListenerConfigurationTest.java
@@ -26,8 +26,10 @@ import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.util.Optional;
 
 import org.apache.commons.configuration.DefaultConfigurationBuilder;
+import org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration.RenderingInformation;
 import org.apache.james.mailbox.quota.model.QuotaThreshold;
 import org.junit.jupiter.api.Test;
 
@@ -37,6 +39,10 @@ public class QuotaMailingListenerConfigurationTest {
 
     private static final String SUBJECT_TEMPLATE = "sbj.mustache";
     private static final String BODY_TEMPLATE = "body.mustache";
+    private static final String OTHER_SUBJECT_TEMPLATE = "other_sbj.mustache";
+    private static final String OTHER_BODY_TEMPLATE = "other_body.mustache";
+    private static final String YET_ANOTHER_SUBJECT_TEMPLATE = "yet_another_sbj.mustache";
+    private static final String YET_ANOTHER_BODY_TEMPLATE = "yet_another_body.mustache";
 
     @Test
     public void shouldMatchBeanContract() {
@@ -51,12 +57,20 @@ public class QuotaMailingListenerConfigurationTest {
         xmlConfiguration.load(toStream(
             "<configuration>\n" +
                 "  <thresholds>\n" +
-                "    <threshold>0.85</threshold>\n" +
-                "    <threshold>0.98</threshold>\n" +
+                "    <threshold>" +
+                "      <value>0.85</value>" +
+                "      <subjectTemplate>" + SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
+                "      <bodyTemplate>" + BODY_TEMPLATE + "</bodyTemplate>\n" +
+                "    </threshold>\n" +
+                "    <threshold>\n" +
+                "      <value>0.98</value>\n" +
+                "      <subjectTemplate>" + OTHER_SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
+                "      <bodyTemplate>" + OTHER_BODY_TEMPLATE + "</bodyTemplate>\n" +
+                "    </threshold>\n" +
                 "  </thresholds>\n" +
+                "  <subjectTemplate>" + YET_ANOTHER_SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
+                "  <bodyTemplate>" + YET_ANOTHER_BODY_TEMPLATE + "</bodyTemplate>\n" +
                 "  <gracePeriod>3 days</gracePeriod>\n" +
-                "  <subjectTemplate>" + SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
-                "  <bodyTemplate>" + BODY_TEMPLATE + "</bodyTemplate>\n" +
                 "  <name>listener-name</name>\n" +
                 "</configuration>"));
 
@@ -64,11 +78,50 @@ public class QuotaMailingListenerConfigurationTest {
 
         assertThat(result)
             .isEqualTo(QuotaMailingListenerConfiguration.builder()
-                .addThresholds(new QuotaThreshold(0.85),
-                    new QuotaThreshold(0.98))
+                .addThreshold(new QuotaThreshold(0.85),
+                    RenderingInformation.from(BODY_TEMPLATE, SUBJECT_TEMPLATE))
+                .addThreshold(new QuotaThreshold(0.98),
+                    RenderingInformation.from(OTHER_BODY_TEMPLATE, OTHER_SUBJECT_TEMPLATE))
+                .gracePeriod(Duration.ofDays(3))
+                .subjectTemplate(YET_ANOTHER_SUBJECT_TEMPLATE)
+                .bodyTemplate(YET_ANOTHER_BODY_TEMPLATE)
+                .name("listener-name")
+                .build());
+    }
+
+    @Test
+    public void fromShouldReadXMLConfigurationWhenRenderingInformationPartiallyOmited() throws Exception {
+        DefaultConfigurationBuilder xmlConfiguration = new DefaultConfigurationBuilder();
+        xmlConfiguration.load(toStream(
+            "<configuration>\n" +
+                "  <thresholds>\n" +
+                "    <threshold>" +
+                "      <value>0.85</value>" +
+                "      <bodyTemplate>" + BODY_TEMPLATE + "</bodyTemplate>\n" +
+                "    </threshold>\n" +
+                "    <threshold>\n" +
+                "      <value>0.98</value>\n" +
+                "      <subjectTemplate>" + OTHER_SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
+                "    </threshold>\n" +
+                "    <threshold>\n" +
+                "      <value>0.99</value>\n" +
+                "    </threshold>\n" +
+                "  </thresholds>\n" +
+                "  <gracePeriod>3 days</gracePeriod>\n" +
+                "  <name>listener-name</name>\n" +
+                "</configuration>"));
+
+        QuotaMailingListenerConfiguration result = QuotaMailingListenerConfiguration.from(xmlConfiguration);
+
+        assertThat(result)
+            .isEqualTo(QuotaMailingListenerConfiguration.builder()
+                .addThreshold(new QuotaThreshold(0.85),
+                    RenderingInformation.from(Optional.of(BODY_TEMPLATE), Optional.empty()))
+                .addThreshold(new QuotaThreshold(0.98),
+                    RenderingInformation.from(Optional.empty(), Optional.of(OTHER_SUBJECT_TEMPLATE)))
+                .addThreshold(new QuotaThreshold(0.99),
+                    RenderingInformation.from(Optional.empty(), Optional.empty()))
                 .gracePeriod(Duration.ofDays(3))
-                .subjectTemplate(SUBJECT_TEMPLATE)
-                .bodyTemplate(BODY_TEMPLATE)
                 .name("listener-name")
                 .build());
     }
@@ -80,8 +133,6 @@ public class QuotaMailingListenerConfigurationTest {
             "<configuration>\n" +
                 "  <thresholds></thresholds>\n" +
                 "  <gracePeriod>3 days</gracePeriod>\n" +
-                "  <subjectTemplate>" + SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
-                "  <bodyTemplate>" + BODY_TEMPLATE + "</bodyTemplate>\n" +
                 "</configuration>"));
 
         QuotaMailingListenerConfiguration result = QuotaMailingListenerConfiguration.from(xmlConfiguration);
@@ -89,8 +140,6 @@ public class QuotaMailingListenerConfigurationTest {
         assertThat(result)
             .isEqualTo(QuotaMailingListenerConfiguration.builder()
                 .gracePeriod(Duration.ofDays(3))
-                .subjectTemplate(SUBJECT_TEMPLATE)
-                .bodyTemplate(BODY_TEMPLATE)
                 .build());
     }
 
@@ -156,9 +205,18 @@ public class QuotaMailingListenerConfigurationTest {
 
     @Test
     public void fromShouldThrowOnEmptySubjectTemplate() throws Exception {
-        DefaultConfigurationBuilder xmlConfiguration = new DefaultConfigurationBuilder();
-        xmlConfiguration.load(toStream(
-            "<configuration><subjectTemplate></subjectTemplate></configuration>"));
+        DefaultConfigurationBuilder xmlConfiguration = new DefaultConfigurationBuilder();xmlConfiguration.load(toStream(
+            "<configuration>\n" +
+                "  <thresholds>\n" +
+                "    <threshold>" +
+                "      <value>0.85</value>" +
+                "      <subjectTemplate></subjectTemplate>\n" +
+                "      <bodyTemplate>" + BODY_TEMPLATE + "</bodyTemplate>\n" +
+                "    </threshold>\n" +
+                "  </thresholds>\n" +
+                "  <gracePeriod>3 days</gracePeriod>\n" +
+                "  <name>listener-name</name>\n" +
+                "</configuration>"));
 
         assertThatThrownBy(() -> QuotaMailingListenerConfiguration.from(xmlConfiguration))
             .isInstanceOf(IllegalArgumentException.class);
@@ -166,9 +224,17 @@ public class QuotaMailingListenerConfigurationTest {
 
     @Test
     public void fromShouldThrowOnEmptyBodyTemplate() throws Exception {
-        DefaultConfigurationBuilder xmlConfiguration = new DefaultConfigurationBuilder();
-        xmlConfiguration.load(toStream(
-            "<configuration><bodyTemplate></bodyTemplate></configuration>"));
+        DefaultConfigurationBuilder xmlConfiguration = new DefaultConfigurationBuilder();xmlConfiguration.load(toStream(
+            "<configuration>\n" +
+                "  <thresholds>\n" +
+                "    <threshold>" +
+                "      <value>0.85</value>" +
+                "      <subjectTemplate>" + SUBJECT_TEMPLATE + "</subjectTemplate>\n" +
+                "      <bodyTemplate></bodyTemplate>\n" +
+                "    </threshold>\n" +
+                "  </thresholds>\n" +
+                "  <name>listener-name</name>\n" +
+                "</configuration>"));
 
         assertThatThrownBy(() -> QuotaMailingListenerConfiguration.from(xmlConfiguration))
             .isInstanceOf(IllegalArgumentException.class);

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNoticeTest.java
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNoticeTest.java b/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNoticeTest.java
index 3bdc35b..071c063 100644
--- a/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNoticeTest.java
+++ b/mailbox/plugin/quota-mailing/src/test/java/org/apache/james/mailbox/quota/mailing/subscribers/QuotaThresholdNoticeTest.java
@@ -24,6 +24,7 @@ import static org.apache.james.mailbox.quota.model.HistoryEvolution.HighestThres
 import static org.apache.james.mailbox.quota.model.QuotaThresholdFixture.TestConstants.DEFAULT_CONFIGURATION;
 import static org.apache.james.mailbox.quota.model.QuotaThresholdFixture.TestConstants.NOW;
 import static org.apache.james.mailbox.quota.model.QuotaThresholdFixture._80;
+import static org.apache.james.mailbox.quota.model.QuotaThresholdFixture._95;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Optional;
@@ -32,12 +33,15 @@ import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.mailbox.model.Quota;
 import org.apache.james.mailbox.quota.QuotaCount;
 import org.apache.james.mailbox.quota.QuotaSize;
+import org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration;
+import org.apache.james.mailbox.quota.mailing.QuotaMailingListenerConfiguration.RenderingInformation;
 import org.apache.james.mailbox.quota.model.HistoryEvolution;
 import org.apache.james.mailbox.quota.model.QuotaThresholdChange;
 import org.apache.james.mailbox.quota.model.QuotaThresholdFixture.Quotas.Counts;
 import org.apache.james.mailbox.quota.model.QuotaThresholdFixture.Quotas.Sizes;
 import org.apache.james.server.core.JamesServerResourceLoader;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -280,17 +284,134 @@ class QuotaThresholdNoticeTest {
     }
 
     @Test
-    void generateSubjectShouldRenderMustacheTemplate() throws Exception {
-        QuotaThresholdChange countThresholdChange = new QuotaThresholdChange(_80, NOW);
+    void renderingShouldUsePerThresholdTemplate() throws Exception {
+        QuotaMailingListenerConfiguration configuration = QuotaMailingListenerConfiguration.builder()
+            .addThreshold(_80, RenderingInformation.from(
+                "classpath://templates/body1.mustache",
+                "classpath://templates/subject1.mustache"))
+            .addThreshold(_95, RenderingInformation.from(
+                "classpath://templates/body2.mustache",
+                "classpath://templates/subject2.mustache"))
+            .build();
+
+        QuotaThresholdNotice quotaThresholdNotice1 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
+            .sizeQuota(Sizes._82_PERCENT)
+            .countQuota(Counts._UNLIMITED)
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_80, NOW), NotAlreadyReachedDuringGracePeriod))
+            .build()
+            .get();
 
-        assertThat(QuotaThresholdNotice.builder()
-            .withConfiguration(DEFAULT_CONFIGURATION)
+        QuotaThresholdNotice quotaThresholdNotice2 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
+            .sizeQuota(Sizes._992_PERTHOUSAND)
+            .countQuota(Counts._UNLIMITED)
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_95, NOW), NotAlreadyReachedDuringGracePeriod))
+            .build()
+            .get();
+
+        SoftAssertions softly = new SoftAssertions();
+        softly.assertThat(quotaThresholdNotice1.generateSubject(fileSystem))
+            .isEqualTo("[SUBJECT_1]");
+        softly.assertThat(quotaThresholdNotice1.generateReport(fileSystem))
+            .isEqualTo("[BODY_1]");
+        softly.assertThat(quotaThresholdNotice2.generateSubject(fileSystem))
+            .isEqualTo("[SUBJECT_2]");
+        softly.assertThat(quotaThresholdNotice2.generateReport(fileSystem))
+            .isEqualTo("[BODY_2]");
+        softly.assertAll();
+    }
+
+    @Test
+    void renderingShouldUseMostSignificantThreshold() throws Exception {
+        QuotaMailingListenerConfiguration configuration = QuotaMailingListenerConfiguration.builder()
+            .addThreshold(_80, RenderingInformation.from(
+                "classpath://templates/body1.mustache",
+                "classpath://templates/subject1.mustache"))
+            .addThreshold(_95, RenderingInformation.from(
+                "classpath://templates/body2.mustache",
+                "classpath://templates/subject2.mustache"))
+            .build();
+
+        QuotaThresholdNotice quotaThresholdNotice1 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
+            .countQuota(Counts._85_PERCENT)
+            .sizeQuota(Sizes._992_PERTHOUSAND)
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_80, NOW), NotAlreadyReachedDuringGracePeriod))
+            .sizeThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_95, NOW), NotAlreadyReachedDuringGracePeriod))
+            .build()
+            .get();
+
+        SoftAssertions softly = new SoftAssertions();
+        softly.assertThat(quotaThresholdNotice1.generateSubject(fileSystem))
+            .isEqualTo("[SUBJECT_2]");
+        softly.assertThat(quotaThresholdNotice1.generateReport(fileSystem))
+            .isEqualTo("[BODY_2]");
+        softly.assertAll();
+    }
+
+    @Test
+    void renderingShouldDefaultToGlobalValueWhenSpecificThresholdValueIsOmmited() throws Exception {
+        QuotaMailingListenerConfiguration configuration = QuotaMailingListenerConfiguration.builder()
+            .addThreshold(_80, RenderingInformation.from(
+                Optional.empty(),
+                Optional.of("classpath://templates/subject1.mustache")))
+            .addThreshold(_95, RenderingInformation.from(
+                Optional.of("classpath://templates/body1.mustache"),
+                Optional.empty()))
+            .subjectTemplate("classpath://templates/subject2.mustache")
+            .bodyTemplate("classpath://templates/body2.mustache")
+            .build();
+
+        QuotaThresholdNotice quotaThresholdNotice1 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
             .sizeQuota(Sizes._82_PERCENT)
             .countQuota(Counts._UNLIMITED)
-            .countThreshold(HistoryEvolution.higherThresholdReached(countThresholdChange, NotAlreadyReachedDuringGracePeriod))
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_80, NOW), NotAlreadyReachedDuringGracePeriod))
             .build()
-            .get()
-            .generateSubject(fileSystem))
+            .get();
+
+        QuotaThresholdNotice quotaThresholdNotice2 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
+            .sizeQuota(Sizes._992_PERTHOUSAND)
+            .countQuota(Counts._UNLIMITED)
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_95, NOW), NotAlreadyReachedDuringGracePeriod))
+            .build()
+            .get();
+
+        SoftAssertions softly = new SoftAssertions();
+        softly.assertThat(quotaThresholdNotice1.generateSubject(fileSystem))
+            .isEqualTo("[SUBJECT_1]");
+        softly.assertThat(quotaThresholdNotice1.generateReport(fileSystem))
+            .isEqualTo("[BODY_2]");
+        softly.assertThat(quotaThresholdNotice2.generateSubject(fileSystem))
+            .isEqualTo("[SUBJECT_2]");
+        softly.assertThat(quotaThresholdNotice2.generateReport(fileSystem))
+            .isEqualTo("[BODY_1]");
+        softly.assertAll();
+    }
+
+    @Test
+    void renderingShouldDefaultToDefaultValueWhenSpecificThresholdAndGlobalValueIsOmited() throws Exception {
+        QuotaMailingListenerConfiguration configuration = QuotaMailingListenerConfiguration.builder()
+            .addThreshold(_80, RenderingInformation.from(
+                Optional.of("classpath://templates/body2.mustache"),
+                Optional.empty()))
+            .build();
+
+        QuotaThresholdNotice quotaThresholdNotice1 = QuotaThresholdNotice.builder()
+            .withConfiguration(configuration)
+            .sizeQuota(Sizes._82_PERCENT)
+            .countQuota(Counts._UNLIMITED)
+            .countThreshold(HistoryEvolution.higherThresholdReached(new QuotaThresholdChange(_80, NOW), NotAlreadyReachedDuringGracePeriod))
+            .build()
+            .get();
+
+        SoftAssertions softly = new SoftAssertions();
+        softly.assertThat(quotaThresholdNotice1.generateSubject(fileSystem))
             .isEqualTo("Warning: Your email usage just exceeded a configured threshold");
+        softly.assertThat(quotaThresholdNotice1.generateReport(fileSystem))
+            .isEqualTo("[BODY_2]");
+        softly.assertAll();
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/resources/templates/body1.mustache
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/resources/templates/body1.mustache b/mailbox/plugin/quota-mailing/src/test/resources/templates/body1.mustache
new file mode 100644
index 0000000..2c555ba
--- /dev/null
+++ b/mailbox/plugin/quota-mailing/src/test/resources/templates/body1.mustache
@@ -0,0 +1 @@
+[BODY_1]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/resources/templates/body2.mustache
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/resources/templates/body2.mustache b/mailbox/plugin/quota-mailing/src/test/resources/templates/body2.mustache
new file mode 100644
index 0000000..b107965
--- /dev/null
+++ b/mailbox/plugin/quota-mailing/src/test/resources/templates/body2.mustache
@@ -0,0 +1 @@
+[BODY_2]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/resources/templates/subject1.mustache
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/resources/templates/subject1.mustache b/mailbox/plugin/quota-mailing/src/test/resources/templates/subject1.mustache
new file mode 100644
index 0000000..f536d4a
--- /dev/null
+++ b/mailbox/plugin/quota-mailing/src/test/resources/templates/subject1.mustache
@@ -0,0 +1 @@
+[SUBJECT_1]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/mailbox/plugin/quota-mailing/src/test/resources/templates/subject2.mustache
----------------------------------------------------------------------
diff --git a/mailbox/plugin/quota-mailing/src/test/resources/templates/subject2.mustache b/mailbox/plugin/quota-mailing/src/test/resources/templates/subject2.mustache
new file mode 100644
index 0000000..a6c5148
--- /dev/null
+++ b/mailbox/plugin/quota-mailing/src/test/resources/templates/subject2.mustache
@@ -0,0 +1 @@
+[SUBJECT_2]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/resources/listeners.xml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/resources/listeners.xml b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/resources/listeners.xml
index 93fad5f..5dcb054 100644
--- a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/resources/listeners.xml
+++ b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/resources/listeners.xml
@@ -29,7 +29,9 @@
     <class>org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdCrossingListener</class>
     <configuration>
       <thresholds>
-        <threshold>0.1</threshold>
+        <threshold>
+          <value>0.1</value>
+        </threshold>
       </thresholds>
       <name>first</name>
     </configuration>
@@ -38,7 +40,9 @@
     <class>org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdCrossingListener</class>
     <configuration>
       <thresholds>
-        <threshold>0.2</threshold>
+        <threshold>
+          <value>0.2</value>
+        </threshold>
       </thresholds>
       <name>second</name>
     </configuration>

http://git-wip-us.apache.org/repos/asf/james-project/blob/0dd4b49f/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/resources/listeners.xml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/resources/listeners.xml b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/resources/listeners.xml
index 9d0a0fe..6cecff3 100644
--- a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/resources/listeners.xml
+++ b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/resources/listeners.xml
@@ -26,7 +26,9 @@
     <class>org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdCrossingListener</class>
     <configuration>
       <thresholds>
-        <threshold>0.1</threshold>
+        <threshold>
+          <value>0.1</value>
+        </threshold>
       </thresholds>
       <name>first</name>
     </configuration>
@@ -35,7 +37,9 @@
     <class>org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdCrossingListener</class>
     <configuration>
       <thresholds>
-        <threshold>0.2</threshold>
+        <threshold>
+          <value>0.2</value>
+        </threshold>
       </thresholds>
       <name>second</name>
     </configuration>


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