You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/12/14 06:53:27 UTC

[james-project] 05/05: JAMES-3467 Experimental cache for DomainList calls

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

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

commit 4ae9454f6dd040f9b9aea1fb9e59851c074fd988
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 9 12:07:23 2020 +0700

    JAMES-3467 Experimental cache for DomainList calls
---
 .../pages/distributed/configure/domainlist.adoc    |  12 +++
 ...lasticSearchQuotaSearchTestSystemExtension.java |   2 +
 .../MemoryQuotaSearchTestSystemExtension.java      |   2 +
 .../domainlist/cassandra/CacheDomainListTest.java  | 115 +++++++++++++++++++++
 .../james/domainlist/xml/XMLDomainListTest.java    |   2 +-
 .../james/domainlist/jpa/JPADomainListTest.java    |   6 +-
 .../james/domainlist/lib/AbstractDomainList.java   |  42 +++++++-
 .../domainlist/lib/DomainListConfiguration.java    |  50 ++++++++-
 .../lib/AbstractDomainListPrivateMethodsTest.java  |  43 ++++++++
 .../james/domainlist/lib/DomainListContract.java   |  13 ++-
 .../impl/JamesMailetContextTest.java               |   3 +-
 .../james/jmap/routes/JMAPApiRoutesTest.scala      |   2 +
 .../apache/james/smtpserver/SMTPServerTest.java    |   1 -
 .../webadmin/routes/AddressMappingRoutesTest.java  |   8 +-
 .../james/webadmin/routes/DomainsRoutesTest.java   |   6 +-
 .../james/webadmin/routes/GroupsRoutesTest.java    |   2 +
 .../james/webadmin/routes/MappingRoutesTest.java   |   8 +-
 .../webadmin/routes/RegexMappingRoutesTest.java    |   4 +-
 .../routes/ElasticSearchQuotaSearchExtension.java  |   2 +
 .../webadmin/service/ExportServiceTestSystem.java  |   2 +
 src/site/xdoc/server/config-domainlist.xml         |  11 ++
 21 files changed, 307 insertions(+), 29 deletions(-)

diff --git a/docs/modules/servers/pages/distributed/configure/domainlist.adoc b/docs/modules/servers/pages/distributed/configure/domainlist.adoc
index 326fd13..84353b7 100644
--- a/docs/modules/servers/pages/distributed/configure/domainlist.adoc
+++ b/docs/modules/servers/pages/distributed/configure/domainlist.adoc
@@ -27,6 +27,18 @@ The automatic IP detection is to support RFC 2821, Sec 4.1.3, address literals.
 |defaultDomain
 |Set the default domain which will be used if an email is send to a recipient without a domain part.
 If no defaultdomain is set the first domain of the DomainList gets used. If the default is not yet contained by the Domain List, the domain will be created upon start.
+
+|read.cache.enable
+|Experimental. Boolean, defaults to false.
+Whether or not to cache domainlist.contains calls. Enable a faster execution however writes will take time
+to propagate.
+
+|read.cache.expiracy
+|Experimental. String (duration), defaults to 10 seconds (10s). Supported units are ms, s, m, h, d, w, month, y.
+Expiracy of the cache. Longer means less reads are performed to the backend but writes will take longer to propagate.
+Low values (a few seconds) are advised.
+
+
 |===
 
 To override autodetected domainnames simply add explicit domainname elements.
diff --git a/mailbox/plugin/quota-search-elasticsearch/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java b/mailbox/plugin/quota-search-elasticsearch/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java
index 991c5d2..3037ca9 100644
--- a/mailbox/plugin/quota-search-elasticsearch/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java
+++ b/mailbox/plugin/quota-search-elasticsearch/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java
@@ -28,6 +28,7 @@ import org.apache.james.backends.es.DockerElasticSearchSingleton;
 import org.apache.james.backends.es.ElasticSearchIndexer;
 import org.apache.james.backends.es.ReactorElasticSearchClient;
 import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.store.quota.QuotaComponents;
@@ -63,6 +64,7 @@ public class ElasticSearchQuotaSearchTestSystemExtension implements ParameterRes
 
             DNSService dnsService = mock(DNSService.class);
             MemoryDomainList domainList = new MemoryDomainList(dnsService);
+            domainList.configure(DomainListConfiguration.DEFAULT);
             MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
 
             ElasticSearchQuotaMailboxListener listener = new ElasticSearchQuotaMailboxListener(
diff --git a/mailbox/plugin/quota-search-scanning/src/test/java/org/apache/james/quota/search/scanning/MemoryQuotaSearchTestSystemExtension.java b/mailbox/plugin/quota-search-scanning/src/test/java/org/apache/james/quota/search/scanning/MemoryQuotaSearchTestSystemExtension.java
index 23eb317..d4999a0 100644
--- a/mailbox/plugin/quota-search-scanning/src/test/java/org/apache/james/quota/search/scanning/MemoryQuotaSearchTestSystemExtension.java
+++ b/mailbox/plugin/quota-search-scanning/src/test/java/org/apache/james/quota/search/scanning/MemoryQuotaSearchTestSystemExtension.java
@@ -22,6 +22,7 @@ package org.apache.james.quota.search.scanning;
 import static org.mockito.Mockito.mock;
 
 import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.store.quota.QuotaComponents;
@@ -48,6 +49,7 @@ public class MemoryQuotaSearchTestSystemExtension implements ParameterResolver {
 
             DNSService dnsService = mock(DNSService.class);
             MemoryDomainList domainList = new MemoryDomainList(dnsService);
+            domainList.configure(DomainListConfiguration.DEFAULT);
             MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
 
             QuotaComponents quotaComponents = resources.getMailboxManager().getQuotaComponents();
diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/domainlist/cassandra/CacheDomainListTest.java b/server/data/data-cassandra/src/test/java/org/apache/james/domainlist/cassandra/CacheDomainListTest.java
new file mode 100644
index 0000000..c74d661
--- /dev/null
+++ b/server/data/data-cassandra/src/test/java/org/apache/james/domainlist/cassandra/CacheDomainListTest.java
@@ -0,0 +1,115 @@
+/****************************************************************
+ * 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.domainlist.cassandra;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.UnknownHostException;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.StatementRecorder;
+import org.apache.james.core.Domain;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.lib.DomainListContract;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.github.fge.lambdas.Throwing;
+
+import reactor.core.publisher.Flux;
+
+class CacheDomainListTest {
+
+    Domain DOMAIN_1 = Domain.of("domain1.tld");
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraDomainListModule.MODULE);
+
+    CassandraDomainList domainList;
+
+    @BeforeEach
+    public void setUp(CassandraCluster cassandra) throws Exception {
+        domainList = new CassandraDomainList(getDNSServer("localhost"), cassandra.getConf());
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(false)
+            .autoDetectIp(false)
+            .cacheEnabled(true)
+            .build());
+    }
+
+    @Test
+    void containsShouldBeCached(CassandraCluster cassandra) throws DomainListException {
+        domainList.addDomain(DOMAIN_1);
+
+        StatementRecorder statementRecorder = new StatementRecorder();
+        cassandra.getConf().recordStatements(statementRecorder);
+
+        Flux.range(0, 10)
+            .doOnNext(Throwing.consumer(i -> domainList.containsDomain(DOMAIN_1)))
+            .blockLast();
+
+        assertThat(statementRecorder.listExecutedStatements(
+            StatementRecorder.Selector.preparedStatement("SELECT domain FROM domains WHERE domain=:domain;")))
+            .hasSize(1);
+    }
+
+    @Test
+    void additionIsInstant() throws DomainListException {
+        domainList.containsDomain(DOMAIN_1);
+
+        domainList.addDomain(DOMAIN_1);
+
+        assertThat(domainList.containsDomain(DOMAIN_1)).isEqualTo(true);
+    }
+
+    @Test
+    void removalIsNotInstant() throws DomainListException {
+        domainList.addDomain(DOMAIN_1);
+
+        domainList.containsDomain(DOMAIN_1);
+
+        domainList.removeDomain(DOMAIN_1);
+
+        assertThat(domainList.containsDomain(DOMAIN_1)).isEqualTo(true);
+    }
+
+    @Test
+    void listShouldRefreshNewEntriesInCache() throws DomainListException {
+        domainList.containsDomain(DOMAIN_1);
+
+        domainList.addDomain(DOMAIN_1);
+
+        domainList.getDomains();
+
+        assertThat(domainList.containsDomain(DOMAIN_1)).isEqualTo(true);
+    }
+
+    private DNSService getDNSServer(final String hostName) throws UnknownHostException {
+        return new InMemoryDNSService()
+            .registerMxRecord(hostName, "127.0.0.1")
+            .registerMxRecord("127.0.0.1", "127.0.0.1");
+    }
+}
diff --git a/server/data/data-file/src/test/java/org/apache/james/domainlist/xml/XMLDomainListTest.java b/server/data/data-file/src/test/java/org/apache/james/domainlist/xml/XMLDomainListTest.java
index c3b93e9..fb4bc9a 100644
--- a/server/data/data-file/src/test/java/org/apache/james/domainlist/xml/XMLDomainListTest.java
+++ b/server/data/data-file/src/test/java/org/apache/james/domainlist/xml/XMLDomainListTest.java
@@ -93,7 +93,7 @@ class XMLDomainListTest {
             .addConfiguredDomains(Domain.of("domain1."))
             .defaultDomain(DEFAULT_DOMAIN));
 
-        assertThat(dom.getDomains()).hasSize(3);
+        assertThat(dom.getDomains()).contains(Domain.of("local"));
     }
 
     @Test
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/domainlist/jpa/JPADomainListTest.java b/server/data/data-jpa/src/test/java/org/apache/james/domainlist/jpa/JPADomainListTest.java
index 5ffb165..2a9bb30 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/domainlist/jpa/JPADomainListTest.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/domainlist/jpa/JPADomainListTest.java
@@ -45,7 +45,11 @@ class JPADomainListTest implements DomainListContract {
     public void tearDown() throws Exception {
         DomainList domainList = createDomainList();
         for (Domain domain: domainList.getDomains()) {
-            domainList.removeDomain(domain);
+            try {
+                domainList.removeDomain(domain);
+            } catch (Exception e) {
+                // silent: exception arise where clearing auto detected domains
+            }
         }
     }
 
diff --git a/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/AbstractDomainList.java b/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/AbstractDomainList.java
index b8d1f30..4053a73 100644
--- a/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/AbstractDomainList.java
+++ b/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/AbstractDomainList.java
@@ -24,6 +24,7 @@ import java.net.UnknownHostException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Stream;
 
 import org.apache.commons.configuration2.HierarchicalConfiguration;
@@ -42,6 +43,9 @@ import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
@@ -65,6 +69,7 @@ public abstract class AbstractDomainList implements DomainList, Configurable {
 
     private final DNSService dns;
     private final EnvDetector envDetector;
+    private LoadingCache<Domain, Boolean> cache;
     private DomainListConfiguration configuration;
     private Domain defaultDomain;
 
@@ -87,6 +92,15 @@ public abstract class AbstractDomainList implements DomainList, Configurable {
     public void configure(DomainListConfiguration domainListConfiguration) throws ConfigurationException {
         this.configuration = domainListConfiguration;
 
+        this.cache = CacheBuilder.newBuilder()
+            .expireAfterAccess(configuration.getCacheExpiracy())
+            .build(new CacheLoader<>() {
+                @Override
+                public Boolean load(Domain key) throws DomainListException {
+                    return containsDomainInternal(key);
+                }
+            });
+
         configureDefaultDomain(domainListConfiguration.getDefaultDomain());
 
         addEnvDomain();
@@ -153,16 +167,34 @@ public abstract class AbstractDomainList implements DomainList, Configurable {
 
     @Override
     public boolean containsDomain(Domain domain) throws DomainListException {
-        boolean internalAnswer = containsDomainInternal(domain);
-        return internalAnswer || getDomains().contains(domain);
+        if (configuration.isCacheEnabled()) {
+            try {
+                boolean internalAnswer = cache.get(domain);
+                return internalAnswer || getDomains().contains(domain);
+            } catch (ExecutionException e) {
+                if (e.getCause() instanceof DomainListException) {
+                    throw (DomainListException) e.getCause();
+                }
+                throw new RuntimeException(e);
+            }
+        } else {
+            boolean internalAnswer = containsDomainInternal(domain);
+            return internalAnswer || getDomains().contains(domain);
+        }
     }
 
     @Override
     public ImmutableList<Domain> getDomains() throws DomainListException {
-        ImmutableSet<Domain> allDomains = getDomainsWithType().values()
+        Multimap<DomainType, Domain> domainsWithType = getDomainsWithType();
+        ImmutableSet<Domain> allDomains = domainsWithType.values()
             .stream()
             .collect(Guavate.toImmutableSet());
 
+        if (configuration.isCacheEnabled()) {
+            domainsWithType.get(DomainType.Internal)
+                .forEach(domain -> cache.put(domain, true));
+        }
+
         if (LOGGER.isDebugEnabled()) {
             for (Domain domain : allDomains) {
                 LOGGER.debug("Handling mail for: " + domain.name());
@@ -192,7 +224,7 @@ public abstract class AbstractDomainList implements DomainList, Configurable {
     }
 
     private ImmutableList<Domain> detectIps(Collection<Domain> domains) {
-        if (configuration.isAutoDetect() ) {
+        if (configuration.isAutoDetectIp()) {
             return getDomainsIpStream(domains, dns, LOGGER)
                 .collect(Guavate.toImmutableList());
         }
@@ -200,7 +232,7 @@ public abstract class AbstractDomainList implements DomainList, Configurable {
     }
 
     private ImmutableList<Domain> detectDomains() {
-        if (configuration.isAutoDetect() ) {
+        if (configuration.isAutoDetect()) {
             String hostName;
             try {
                 hostName = dns.getHostName(dns.getLocalHost());
diff --git a/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/DomainListConfiguration.java b/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/DomainListConfiguration.java
index 0aa03ef..5239c58 100644
--- a/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/DomainListConfiguration.java
+++ b/server/data/data-library/src/main/java/org/apache/james/domainlist/lib/DomainListConfiguration.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.domainlist.lib;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -29,6 +30,7 @@ import java.util.function.Predicate;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.core.Domain;
+import org.apache.james.util.DurationParser;
 import org.apache.james.util.StreamUtils;
 
 import com.github.steveash.guavate.Guavate;
@@ -39,6 +41,8 @@ public class DomainListConfiguration {
         private Optional<Boolean> autoDetectIp;
         private Optional<Boolean> autoDetect;
         private Optional<Domain> defaultDomain;
+        private Optional<Boolean> cacheEnabled;
+        private Optional<Duration> cacheExpiracy;
         private ImmutableList.Builder<Domain> configuredDomains;
 
         public Builder() {
@@ -46,6 +50,8 @@ public class DomainListConfiguration {
             autoDetect = Optional.empty();
             defaultDomain = Optional.empty();
             configuredDomains = ImmutableList.builder();
+            cacheEnabled = Optional.empty();
+            cacheExpiracy = Optional.empty();
         }
 
         public Builder defaultDomain(Domain defaultDomain) {
@@ -63,6 +69,16 @@ public class DomainListConfiguration {
             return this;
         }
 
+        public Builder cacheEnabled(boolean cacheEnabled) {
+            this.cacheEnabled = Optional.of(cacheEnabled);
+            return this;
+        }
+
+        public Builder cacheExpiracy(Duration cacheExpiracy) {
+            this.cacheExpiracy = Optional.of(cacheExpiracy);
+            return this;
+        }
+
         public Builder defaultDomain(Optional<Domain> defaultDomain) {
             this.defaultDomain = defaultDomain;
             return this;
@@ -73,6 +89,16 @@ public class DomainListConfiguration {
             return this;
         }
 
+        public Builder cacheEnabled(Optional<Boolean> cacheEnabled) {
+            this.cacheEnabled = cacheEnabled;
+            return this;
+        }
+
+        public Builder cacheExpiracy(Optional<Duration> cacheExpiracy) {
+            this.cacheExpiracy = cacheExpiracy;
+            return this;
+        }
+
         public Builder autoDetectIp(Optional<Boolean> autoDetectIp) {
             this.autoDetectIp = autoDetectIp;
             return this;
@@ -97,16 +123,21 @@ public class DomainListConfiguration {
                 autoDetectIp.orElse(false),
                 autoDetect.orElse(false),
                 defaultDomain.orElse(Domain.LOCALHOST),
-                configuredDomains.build());
+                configuredDomains.build(),
+                cacheEnabled.orElse(false),
+                cacheExpiracy.orElse(DEFAULT_EXPIRACY));
         }
     }
 
+    public static final Duration DEFAULT_EXPIRACY = Duration.ofSeconds(10);
     public static DomainListConfiguration DEFAULT = builder().build();
 
     public static final String CONFIGURE_AUTODETECT = "autodetect";
     public static final String CONFIGURE_AUTODETECT_IP = "autodetectIP";
     public static final String CONFIGURE_DEFAULT_DOMAIN = "defaultDomain";
     public static final String CONFIGURE_DOMAIN_NAMES = "domainnames.domainname";
+    public static final String ENABLE_READ_CACHE = "read.cache.enable";
+    public static final String READ_CACHE_EXPIRACY = "read.cache.expiracy";
 
     public static Builder builder() {
         return new Builder();
@@ -124,6 +155,9 @@ public class DomainListConfiguration {
             .defaultDomain(Optional.ofNullable(config.getString(CONFIGURE_DEFAULT_DOMAIN, null))
                 .map(Domain::of))
             .addConfiguredDomains(configuredDomains)
+            .cacheEnabled(Optional.ofNullable(config.getBoolean(ENABLE_READ_CACHE, null)))
+            .cacheExpiracy(Optional.ofNullable(config.getString(READ_CACHE_EXPIRACY, null))
+                .map(DurationParser::parse))
             .build();
     }
 
@@ -131,12 +165,16 @@ public class DomainListConfiguration {
     private final boolean autoDetect;
     private final Domain defaultDomain;
     private final List<Domain> configuredDomains;
+    private final boolean cacheEnabled;
+    private final Duration cacheExpiracy;
 
-    public DomainListConfiguration(boolean autoDetectIp, boolean autoDetect, Domain defaultDomain, List<Domain> configuredDomains) {
+    public DomainListConfiguration(boolean autoDetectIp, boolean autoDetect, Domain defaultDomain, List<Domain> configuredDomains, boolean cacheEnabled, Duration cacheExpiracy) {
         this.autoDetectIp = autoDetectIp;
         this.autoDetect = autoDetect;
         this.defaultDomain = defaultDomain;
         this.configuredDomains = configuredDomains;
+        this.cacheEnabled = cacheEnabled;
+        this.cacheExpiracy = cacheExpiracy;
     }
 
     public boolean isAutoDetectIp() {
@@ -155,6 +193,14 @@ public class DomainListConfiguration {
         return configuredDomains;
     }
 
+    public boolean isCacheEnabled() {
+        return cacheEnabled;
+    }
+
+    public Duration getCacheExpiracy() {
+        return cacheExpiracy;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof DomainListConfiguration) {
diff --git a/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/AbstractDomainListPrivateMethodsTest.java b/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/AbstractDomainListPrivateMethodsTest.java
index 8dfe84f..2a5a166 100644
--- a/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/AbstractDomainListPrivateMethodsTest.java
+++ b/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/AbstractDomainListPrivateMethodsTest.java
@@ -82,6 +82,10 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldSetFromConfigurationWhenDifferentFromLocalhost() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
         String expectedDefaultDomain = "myDomain.org";
 
         domainList.configureDefaultDomain(Domain.of(expectedDefaultDomain));
@@ -91,6 +95,10 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldSetFromHostnameWhenEqualsToLocalhost() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
         Domain expectedDefaultDomain = Domain.of(InetAddress.getLocalHost().getHostName());
         domainList.configureDefaultDomain(Domain.LOCALHOST);
 
@@ -99,6 +107,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldCreateFromHostnameWhenEqualsToLocalhost() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         Domain expectedDefaultDomain = Domain.of(InetAddress.getLocalHost().getHostName());
         domainList.configureDefaultDomain(expectedDefaultDomain);
 
@@ -107,6 +120,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldNotCreateTwiceWhenCallingTwoTimes() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         Domain expectedDefaultDomain = Domain.of(InetAddress.getLocalHost().getHostName());
         domainList.configureDefaultDomain(expectedDefaultDomain);
         domainList.configureDefaultDomain(expectedDefaultDomain);
@@ -116,6 +134,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldAddDomainWhenNotContained() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         Domain expectedDefaultDomain = Domain.of("myDomain.org");
 
         domainList.configureDefaultDomain(expectedDefaultDomain);
@@ -125,6 +148,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void setDefaultDomainShouldNotFailWhenDomainContained() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         Domain expectedDefaultDomain = Domain.of("myDomain.org");
 
         domainList.addDomain(expectedDefaultDomain);
@@ -250,6 +278,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void containsDomainShouldReturnDetectedIp() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         String detected = "detected.tld";
         String detectedIp = "148.25.32.1";
         when(dnsService.getLocalHost()).thenReturn(InetAddress.getByName("127.0.0.1"));
@@ -335,6 +368,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void removeDomainShouldThrowWhenRemovingAutoDetectedIps() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         String detected = "detected.tld";
         String detectedIp = "148.25.32.1";
         when(dnsService.getLocalHost()).thenReturn(InetAddress.getByName("127.0.0.1"));
@@ -349,6 +387,11 @@ class AbstractDomainListPrivateMethodsTest {
 
     @Test
     void removeDomainShouldThrowWhenRemovingDefaultDomain() throws Exception {
+        domainList.configure(DomainListConfiguration.builder()
+            .autoDetect(true)
+            .autoDetectIp(true)
+            .build());
+
         Domain defaultDomain = Domain.of("default.tld");
         domainList.configureDefaultDomain(defaultDomain);
 
diff --git a/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/DomainListContract.java b/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/DomainListContract.java
index 9388bea..ae02aeb 100644
--- a/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/DomainListContract.java
+++ b/server/data/data-library/src/test/java/org/apache/james/domainlist/lib/DomainListContract.java
@@ -50,13 +50,15 @@ public interface DomainListContract {
         domainList().addDomain(DOMAIN_3);
         domainList().addDomain(DOMAIN_4);
         domainList().addDomain(DOMAIN_5);
-        assertThat(domainList().getDomains()).containsOnly(DOMAIN_3, DOMAIN_4, DOMAIN_5);
+        assertThat(domainList().getDomains()).containsOnly(DOMAIN_3, DOMAIN_4, DOMAIN_5,
+            Domain.LOCALHOST /*default domain*/);
     }
 
     @Test
     default void domainsShouldBeListedInLowerCase() throws DomainListException {
         domainList().addDomain(DOMAIN_UPPER_5);
-        assertThat(domainList().getDomains()).containsOnly(DOMAIN_5);
+        assertThat(domainList().getDomains()).containsOnly(DOMAIN_5,
+            Domain.LOCALHOST /*default domain*/);
     }
 
     @Test
@@ -73,14 +75,14 @@ public interface DomainListContract {
 
     @Test
     default void listDomainsShouldReturnNullWhenThereIsNoDomains() throws DomainListException {
-        assertThat(domainList().getDomains()).isEmpty();
+        assertThat(domainList().getDomains()).containsOnly(Domain.LOCALHOST /*default domain*/);
     }
 
     @Test
     default void testAddRemoveContainsSameDomain() throws DomainListException {
         domainList().addDomain(DOMAIN_1);
         domainList().removeDomain(DOMAIN_1);
-        assertThat(domainList().getDomains()).isEmpty();
+        assertThat(domainList().getDomains()).containsOnly(Domain.LOCALHOST /*default domain*/);
     }
 
     @Test
@@ -102,7 +104,8 @@ public interface DomainListContract {
         } catch (DomainListException e) {
             LOGGER.info("Ignored error", e);
         }
-        assertThat(domainList().getDomains()).containsOnly(DOMAIN_1);
+        assertThat(domainList().getDomains()).containsOnly(DOMAIN_1,
+            Domain.LOCALHOST /*default domain*/);
     }
 
     @Test
diff --git a/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailetContextTest.java b/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailetContextTest.java
index 3434d08..22e14fb 100644
--- a/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailetContextTest.java
+++ b/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailetContextTest.java
@@ -83,8 +83,7 @@ public class JamesMailetContextTest {
     @SuppressWarnings("unchecked")
     public void setUp() throws Exception {
         domainList = spy(new MemoryDomainList(DNS_SERVICE));
-        domainList.configure(DomainListConfiguration.builder()
-            .build());
+        domainList.configure(DomainListConfiguration.DEFAULT);
 
         usersRepository = spy(MemoryUsersRepository.withVirtualHosting(domainList));
         recipientRewriteTable = spy(new MemoryRecipientRewriteTable());
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index d600b2d..b8bb281 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -33,6 +33,7 @@ import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.http.HttpStatus
 import org.apache.james.core.{Domain, Username}
 import org.apache.james.dnsservice.api.DNSService
+import org.apache.james.domainlist.lib.DomainListConfiguration
 import org.apache.james.domainlist.memory.MemoryDomainList
 import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE}
@@ -63,6 +64,7 @@ object JMAPApiRoutesTest {
   private val empty_set: ImmutableSet[PreDeletionHook] = ImmutableSet.of()
   private val dnsService = mock(classOf[DNSService])
   private val domainList = new MemoryDomainList(dnsService)
+  domainList.configure(DomainListConfiguration.DEFAULT)
   domainList.addDomain(Domain.of("james.org"))
 
   private val usersRepository = MemoryUsersRepository.withoutVirtualHosting(domainList)
diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
index 0b7d3ba..f327ffe 100644
--- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
+++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
@@ -220,7 +220,6 @@ public class SMTPServerTest {
             .autoDetectIp(false)
             .build());
 
-        domainList.addDomain(Domain.LOCALHOST);
         domainList.addDomain(Domain.of(LOCAL_DOMAIN));
         domainList.addDomain(Domain.of("examplebis.local"));
         usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java
index 6f82abb..8e701f7 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java
@@ -29,8 +29,7 @@ import java.util.Map;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.dnsservice.api.DNSService;
-import org.apache.james.domainlist.api.DomainList;
-import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.lib.Mapping;
@@ -56,10 +55,11 @@ class AddressMappingRoutesTest {
     private MemoryRecipientRewriteTable recipientRewriteTable;
 
     @BeforeEach
-    void setUp() throws DomainListException {
+    void setUp() throws Exception {
         recipientRewriteTable = new MemoryRecipientRewriteTable();
         DNSService dnsService = mock(DNSService.class);
-        DomainList domainList = new MemoryDomainList(dnsService);
+        MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
         domainList.addDomain(Domain.of("domain.tld"));
 
         recipientRewriteTable.setDomainList(domainList);
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
index 99275cc..9b941a3 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
@@ -106,7 +106,7 @@ class DomainsRoutesTest {
             .then()
                 .statusCode(HttpStatus.OK_200)
                 .contentType(ContentType.JSON)
-                .body(".", hasSize(0));
+                .body(".", contains("localhost"));
         }
 
         @Test
@@ -143,7 +143,7 @@ class DomainsRoutesTest {
             .then()
                 .contentType(ContentType.JSON)
                 .statusCode(HttpStatus.OK_200)
-                .body(".", contains(DOMAIN));
+                .body(".", containsInAnyOrder(DOMAIN, "localhost"));
         }
 
         @Test
@@ -251,7 +251,7 @@ class DomainsRoutesTest {
             .then()
                 .contentType(ContentType.JSON)
                 .statusCode(HttpStatus.OK_200)
-                .body(".", hasSize(0));
+                .body(".", contains("localhost"));
         }
 
         @Test
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java
index 4403e6f..d27fef5 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java
@@ -39,6 +39,7 @@ import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
@@ -106,6 +107,7 @@ class GroupsRoutesTest {
             memoryRecipientRewriteTable = new MemoryRecipientRewriteTable();
             DNSService dnsService = mock(DNSService.class);
             domainList = new MemoryDomainList(dnsService);
+            domainList.configure(DomainListConfiguration.DEFAULT);
             domainList.addDomain(DOMAIN);
             domainList.addDomain(ALIAS_DOMAIN);
             domainList.addDomain(DOMAIN_MAPPING);
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
index ee86bf1..a06a429 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
@@ -28,8 +28,7 @@ import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
-import org.apache.james.domainlist.api.DomainList;
-import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
@@ -63,11 +62,12 @@ class MappingRoutesTest {
     private MemoryRecipientRewriteTable recipientRewriteTable;
 
     @BeforeEach
-    void setUp() throws DomainListException {
+    void setUp() throws Exception {
         JsonTransformer jsonTransformer = new JsonTransformer();
         recipientRewriteTable = new MemoryRecipientRewriteTable();
         DNSService dnsService = mock(DNSService.class);
-        DomainList domainList = new MemoryDomainList(dnsService);
+        MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
         domainList.addDomain(Domain.of("domain.tld"));
         domainList.addDomain(Domain.of("aliasdomain.tld"));
         domainList.addDomain(Domain.of("domain.mapping.tld"));
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
index 6afc8a4..bdd7eba 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
@@ -28,6 +28,7 @@ import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.MappingSource;
@@ -50,7 +51,8 @@ class RegexMappingRoutesTest {
     @BeforeEach
     void beforeEach() throws Exception {
         DNSService dnsService = mock(DNSService.class);
-        DomainList domainList = new MemoryDomainList(dnsService);
+        MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
         memoryRecipientRewriteTable = new MemoryRecipientRewriteTable();
         memoryRecipientRewriteTable.setDomainList(domainList);
         domainList.addDomain(Domain.of("domain.tld"));
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ElasticSearchQuotaSearchExtension.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ElasticSearchQuotaSearchExtension.java
index 392b859..dbfbde3 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ElasticSearchQuotaSearchExtension.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ElasticSearchQuotaSearchExtension.java
@@ -29,6 +29,7 @@ import org.apache.james.backends.es.ElasticSearchConfiguration;
 import org.apache.james.backends.es.ElasticSearchIndexer;
 import org.apache.james.backends.es.ReactorElasticSearchClient;
 import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.store.quota.QuotaComponents;
@@ -72,6 +73,7 @@ public class ElasticSearchQuotaSearchExtension implements ParameterResolver, Bef
 
             DNSService dnsService = mock(DNSService.class);
             MemoryDomainList domainList = new MemoryDomainList(dnsService);
+            domainList.configure(DomainListConfiguration.DEFAULT);
             MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
 
             ElasticSearchQuotaMailboxListener listener = new ElasticSearchQuotaMailboxListener(
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
index c7b0b94..ff1133d 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
@@ -33,6 +33,7 @@ import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.mailbox.MailboxSession;
@@ -87,6 +88,7 @@ public class ExportServiceTestSystem {
 
     private MemoryUsersRepository createUsersRepository(DNSService dnsService) throws Exception {
         MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
         MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
 
         domainList.addDomain(DOMAIN);
diff --git a/src/site/xdoc/server/config-domainlist.xml b/src/site/xdoc/server/config-domainlist.xml
index eb0e61d..6b95597 100644
--- a/src/site/xdoc/server/config-domainlist.xml
+++ b/src/site/xdoc/server/config-domainlist.xml
@@ -47,6 +47,17 @@ If autodetect is false, James will use only the specified domainnames. Defaults
         <dt><strong>autodetectIP</strong></dt>
         <dd>true or false - If autodetectIP is not false, James will also allow add the IP address for each servername. 
 The automatic IP detection is to support RFC 2821, Sec 4.1.3, address literals. Defaults to false.</dd>
+
+        <dt><strong>read.cache.enable</strong></dt>
+        <dd>Experimental. Boolean, defaults to false.
+            Whether or not to cache domainlist.contains calls. Enable a faster execution however writes will take time
+            to propagate.</dd>
+
+        <dt><strong>read.cache.expiracy</strong></dt>
+        <dd>Experimental. String (duration), defaults to 10 seconds (10s). Supported units are ms, s, m, h, d, w, month, y.
+            Expiracy of the cache. Longer means less reads are performed to the backend but writes will take longer to propagate.
+            Low values (a few seconds) are advised.</dd>
+
         <dt><strong>defaultDomain</strong></dt>
         <dd>Set the default domain which will be used if an email is send to a recipient without a domain part.
             If not defaultdomain is set the first domain of the DomainList get used. If the default is not yet contained by the Domain List, the domain will be created upon start.</dd>


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