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 bt...@apache.org on 2019/05/20 09:39:59 UTC

[james-project] 02/09: JAMES-2765 Duplicate apache-james-mailbox-quota-search-elasticsearch

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 323762fcf1df8495dec6f60622050d98f3b501ac
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu May 16 16:26:13 2019 +0700

    JAMES-2765 Duplicate apache-james-mailbox-quota-search-elasticsearch
    
    Duplicated project will be migrated to ES-6
---
 .../plugin/quota-search-elasticsearch-v6/pom.xml   | 142 +++++++++++++++++++
 .../ElasticSearchQuotaConfiguration.java           | 152 +++++++++++++++++++++
 .../elasticsearch/ElasticSearchQuotaSearcher.java  |  91 ++++++++++++
 .../search/elasticsearch/QuotaQueryConverter.java  | 103 ++++++++++++++
 .../QuotaRatioElasticSearchConstants.java          |  37 +++++
 .../elasticsearch/QuotaRatioMappingFactory.java    |  67 +++++++++
 .../QuotaSearchIndexCreationUtil.java              |  55 ++++++++
 .../events/ElasticSearchQuotaMailboxListener.java  |  70 ++++++++++
 .../elasticsearch/json/JsonMessageConstants.java   |  28 ++++
 .../elasticsearch/json/QuotaRatioAsJson.java       | 120 ++++++++++++++++
 .../json/QuotaRatioToElasticSearchJson.java        |  49 +++++++
 .../ElasticSearchQuotaConfigurationTest.java       | 104 ++++++++++++++
 ...lasticSearchQuotaSearchTestSystemExtension.java | 110 +++++++++++++++
 .../ElasticSearchQuotaSearcherTest.java            |  28 ++++
 .../elasticsearch/QuotaQueryConverterTest.java     |  83 +++++++++++
 .../ElasticSearchQuotaMailboxListenerTest.java     | 105 ++++++++++++++
 .../elasticsearch/json/QuotaRatioAsJsonTest.java   | 107 +++++++++++++++
 .../json/QuotaRatioToElasticSearchJsonTest.java    |  80 +++++++++++
 .../src/test/resources/quotaRatio.json             |   1 +
 .../src/test/resources/quotaRatioNoDomain.json     |   1 +
 mailbox/pom.xml                                    |   1 +
 21 files changed, 1534 insertions(+)

diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/pom.xml b/mailbox/plugin/quota-search-elasticsearch-v6/pom.xml
new file mode 100644
index 0000000..c1eeca6
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/pom.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>apache-james-mailbox</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.4.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>apache-james-mailbox-quota-search-elasticsearch-v6</artifactId>
+    <name>Apache James :: Mailbox :: Plugin :: Quota Search :: ElasticSearch :: v6</name>
+    <description>Apache James Mailbox ElasticSearch v6 implementation for quota search</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-backends-es</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-backends-es</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-quota-search</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-quota-search</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jdk8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-migrationsupport</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfiguration.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfiguration.java
new file mode 100644
index 0000000..c14f339
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfiguration.java
@@ -0,0 +1,152 @@
+/*
+ * 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.quota.search.elasticsearch;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.james.backends.es.IndexName;
+import org.apache.james.backends.es.ReadAliasName;
+import org.apache.james.backends.es.WriteAliasName;
+
+public class ElasticSearchQuotaConfiguration {
+
+    public static class Builder {
+
+        private Optional<IndexName> indexQuotaRatioName;
+        private Optional<ReadAliasName> readAliasQuotaRatioName;
+        private Optional<WriteAliasName> writeAliasQuotaRatioName;
+
+        public Builder() {
+            indexQuotaRatioName = Optional.empty();
+            readAliasQuotaRatioName = Optional.empty();
+            writeAliasQuotaRatioName = Optional.empty();
+        }
+
+        public Builder indexQuotaRatioName(IndexName indexQuotaRatioName) {
+            return indexQuotaRatioName(Optional.of(indexQuotaRatioName));
+        }
+
+        public Builder indexQuotaRatioName(Optional<IndexName> indexQuotaRatioName) {
+            this.indexQuotaRatioName = indexQuotaRatioName;
+            return this;
+        }
+
+        public Builder readAliasQuotaRatioName(ReadAliasName readAliasQuotaRatioName) {
+            return readAliasQuotaRatioName(Optional.of(readAliasQuotaRatioName));
+        }
+
+        public Builder readAliasQuotaRatioName(Optional<ReadAliasName> readAliasQuotaRatioName) {
+            this.readAliasQuotaRatioName = readAliasQuotaRatioName;
+            return this;
+        }
+
+        public Builder writeAliasQuotaRatioName(WriteAliasName writeAliasQuotaRatioName) {
+            return writeAliasQuotaRatioName(Optional.of(writeAliasQuotaRatioName));
+        }
+
+        public Builder writeAliasQuotaRatioName(Optional<WriteAliasName> writeAliasQuotaRatioName) {
+            this.writeAliasQuotaRatioName = writeAliasQuotaRatioName;
+            return this;
+        }
+
+
+        public ElasticSearchQuotaConfiguration build() {
+            return new ElasticSearchQuotaConfiguration(
+                indexQuotaRatioName.orElse(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_INDEX),
+                readAliasQuotaRatioName.orElse(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_READ_ALIAS),
+                writeAliasQuotaRatioName.orElse(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_WRITE_ALIAS));
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static final String ELASTICSEARCH_INDEX_QUOTA_RATIO_NAME = "elasticsearch.index.quota.ratio.name";
+    public static final String ELASTICSEARCH_ALIAS_READ_QUOTA_RATIO_NAME = "elasticsearch.alias.read.quota.ratio.name";
+    public static final String ELASTICSEARCH_ALIAS_WRITE_QUOTA_RATIO_NAME = "elasticsearch.alias.write.quota.ratio.name";
+
+    public static final ElasticSearchQuotaConfiguration DEFAULT_CONFIGURATION = builder().build();
+
+    public static ElasticSearchQuotaConfiguration fromProperties(Configuration configuration) throws ConfigurationException {
+        return builder()
+            .indexQuotaRatioName(computeQuotaSearchIndexName(configuration))
+            .readAliasQuotaRatioName(computeQuotaSearchReadAlias(configuration))
+            .writeAliasQuotaRatioName(computeQuotaSearchWriteAlias(configuration))
+            .build();
+    }
+
+    public static Optional<IndexName> computeQuotaSearchIndexName(Configuration configuration) {
+        return Optional.ofNullable(configuration.getString(ELASTICSEARCH_INDEX_QUOTA_RATIO_NAME))
+            .map(IndexName::new);
+    }
+
+    public static Optional<WriteAliasName> computeQuotaSearchWriteAlias(Configuration configuration) {
+        return Optional.ofNullable(configuration.getString(ELASTICSEARCH_ALIAS_WRITE_QUOTA_RATIO_NAME))
+            .map(WriteAliasName::new);
+    }
+
+    public static Optional<ReadAliasName> computeQuotaSearchReadAlias(Configuration configuration) {
+        return Optional.ofNullable(configuration.getString(ELASTICSEARCH_ALIAS_READ_QUOTA_RATIO_NAME))
+                .map(ReadAliasName::new);
+    }
+
+    private final IndexName indexQuotaRatioName;
+    private final ReadAliasName readAliasQuotaRatioName;
+    private final WriteAliasName writeAliasQuotaRatioName;
+
+    private ElasticSearchQuotaConfiguration(IndexName indexQuotaRatioName, ReadAliasName readAliasQuotaRatioName, WriteAliasName writeAliasQuotaRatioName) {
+        this.indexQuotaRatioName = indexQuotaRatioName;
+        this.readAliasQuotaRatioName = readAliasQuotaRatioName;
+        this.writeAliasQuotaRatioName = writeAliasQuotaRatioName;
+    }
+
+    public IndexName getIndexQuotaRatioName() {
+        return indexQuotaRatioName;
+    }
+
+    public ReadAliasName getReadAliasQuotaRatioName() {
+        return readAliasQuotaRatioName;
+    }
+
+    public WriteAliasName getWriteAliasQuotaRatioName() {
+        return writeAliasQuotaRatioName;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof ElasticSearchQuotaConfiguration) {
+            ElasticSearchQuotaConfiguration that = (ElasticSearchQuotaConfiguration) o;
+
+            return Objects.equals(this.indexQuotaRatioName, that.indexQuotaRatioName)
+                && Objects.equals(this.readAliasQuotaRatioName, that.readAliasQuotaRatioName)
+                && Objects.equals(this.writeAliasQuotaRatioName, that.writeAliasQuotaRatioName);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(indexQuotaRatioName, readAliasQuotaRatioName);
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcher.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcher.java
new file mode 100644
index 0000000..21d55a8
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcher.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import static org.apache.james.quota.search.elasticsearch.QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE;
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.USER;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.james.backends.es.AliasName;
+import org.apache.james.backends.es.ReadAliasName;
+import org.apache.james.backends.es.search.ScrollIterable;
+import org.apache.james.core.User;
+import org.apache.james.quota.search.QuotaQuery;
+import org.apache.james.quota.search.QuotaSearcher;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+
+import com.github.steveash.guavate.Guavate;
+
+public class ElasticSearchQuotaSearcher implements QuotaSearcher {
+    private static final TimeValue TIMEOUT = new TimeValue(60000);
+
+    private final Client client;
+    private final AliasName readAlias;
+    private final QuotaQueryConverter quotaQueryConverter;
+
+    public ElasticSearchQuotaSearcher(Client client, ReadAliasName readAlias) {
+        this.client = client;
+        this.readAlias = readAlias;
+        this.quotaQueryConverter = new QuotaQueryConverter();
+    }
+
+    @Override
+    public List<User> search(QuotaQuery query) {
+        Stream<User> results = new ScrollIterable(client, prepareSearch(query))
+            .stream()
+            .flatMap(searchResponse -> Arrays.stream(searchResponse.getHits()
+                .getHits()))
+            .map(hit -> hit.field(USER))
+            .map(field -> (String) field.getValue())
+            .map(User::fromUsername)
+            .skip(query.getOffset().getValue());
+
+        return query.getLimit().getValue()
+            .map(results::limit)
+            .orElse(results)
+            .collect(Guavate.toImmutableList());
+    }
+
+    public SearchRequestBuilder prepareSearch(QuotaQuery query) {
+        SearchRequestBuilder searchRequestBuilder = client.prepareSearch(readAlias.getValue())
+            .setTypes(QUOTA_RATIO_TYPE.getValue())
+            .setScroll(TIMEOUT)
+            .addFields(USER)
+            .setQuery(quotaQueryConverter.from(query));
+
+        query.getLimit()
+            .getValue()
+            .ifPresent(searchRequestBuilder::setSize);
+
+        searchRequestBuilder.addSort(
+            SortBuilders.fieldSort(USER)
+                .order(SortOrder.ASC));
+
+        return searchRequestBuilder;
+    }
+
+}
\ No newline at end of file
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverter.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverter.java
new file mode 100644
index 0000000..b02d8ac
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverter.java
@@ -0,0 +1,103 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.DOMAIN;
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.QUOTA_RATIO;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.james.quota.search.QuotaClause;
+import org.apache.james.quota.search.QuotaClause.And;
+import org.apache.james.quota.search.QuotaClause.HasDomain;
+import org.apache.james.quota.search.QuotaClause.LessThan;
+import org.apache.james.quota.search.QuotaClause.MoreThan;
+import org.apache.james.quota.search.QuotaQuery;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.RangeQueryBuilder;
+import org.elasticsearch.index.query.TermQueryBuilder;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+
+public class QuotaQueryConverter {
+    private final Map<Class<? extends QuotaClause>, Function<QuotaClause, QueryBuilder>> clauseConverter;
+
+    public QuotaQueryConverter() {
+        Builder<Class<? extends QuotaClause>, Function<QuotaClause, QueryBuilder>> builder = ImmutableMap.builder();
+        
+        builder.put(HasDomain.class, this::convertHasDomain);
+        builder.put(And.class, this::disableNestedAnd);
+        builder.put(MoreThan.class, this::convertMoreThan);
+        builder.put(LessThan.class, this::convertLessThan);
+
+        clauseConverter = builder.build();
+    }
+
+    public QueryBuilder from(QuotaQuery query) {
+        List<QuotaClause> clauses = query.getClause().getClauses();
+        if (clauses.isEmpty()) {
+            return matchAllQuery();
+        }
+        if (clauses.size() == 1) {
+            return singleClauseAsESQuery(clauses.get(0));
+        }
+        
+        return clausesAsAndESQuery(clauses);
+    }
+
+    private BoolQueryBuilder clausesAsAndESQuery(List<QuotaClause> clauses) {
+        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
+        clauses.stream()
+            .map(this::singleClauseAsESQuery)
+            .forEach(boolQueryBuilder::must);
+        return boolQueryBuilder;
+    }
+
+    private QueryBuilder disableNestedAnd(QuotaClause clause) {
+        throw new IllegalArgumentException("Nested \"And\" clauses are not supported");
+    }
+
+    private TermQueryBuilder convertHasDomain(QuotaClause clause) {
+        HasDomain hasDomain = (HasDomain) clause;
+        return termQuery(DOMAIN, hasDomain.getDomain().asString());
+    }
+
+    private RangeQueryBuilder convertMoreThan(QuotaClause clause) {
+        MoreThan moreThan = (MoreThan) clause;
+        return rangeQuery(QUOTA_RATIO).gte(moreThan.getQuotaBoundary().getRatio());
+    }
+
+    private RangeQueryBuilder convertLessThan(QuotaClause clause) {
+        LessThan lessThan = (LessThan) clause;
+        return rangeQuery(QUOTA_RATIO).lte(lessThan.getQuotaBoundary().getRatio());
+    }
+
+    private QueryBuilder singleClauseAsESQuery(QuotaClause clause) {
+        return clauseConverter.get(clause.getClass()).apply(clause);
+    }
+
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioElasticSearchConstants.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioElasticSearchConstants.java
new file mode 100644
index 0000000..e3ceadd
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioElasticSearchConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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.quota.search.elasticsearch;
+
+import org.apache.james.backends.es.IndexName;
+import org.apache.james.backends.es.ReadAliasName;
+import org.apache.james.backends.es.TypeName;
+import org.apache.james.backends.es.WriteAliasName;
+
+public interface QuotaRatioElasticSearchConstants {
+
+    interface InjectionNames {
+        String QUOTA_RATIO = "quotaRatio";
+    }
+
+    WriteAliasName DEFAULT_QUOTA_RATIO_WRITE_ALIAS = new WriteAliasName("quota_ratio_write_alias");
+    ReadAliasName DEFAULT_QUOTA_RATIO_READ_ALIAS = new ReadAliasName("quota_ratio_read_alias");
+    IndexName DEFAULT_QUOTA_RATIO_INDEX = new IndexName("quota_ratio_v1");
+    TypeName QUOTA_RATIO_TYPE = new TypeName("quota_ratio");
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioMappingFactory.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioMappingFactory.java
new file mode 100644
index 0000000..128f1d1
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaRatioMappingFactory.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import static org.apache.james.backends.es.NodeMappingFactory.DOUBLE;
+import static org.apache.james.backends.es.NodeMappingFactory.INDEX;
+import static org.apache.james.backends.es.NodeMappingFactory.NOT_ANALYZED;
+import static org.apache.james.backends.es.NodeMappingFactory.PROPERTIES;
+import static org.apache.james.backends.es.NodeMappingFactory.STRING;
+import static org.apache.james.backends.es.NodeMappingFactory.TYPE;
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.DOMAIN;
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.QUOTA_RATIO;
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.USER;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+
+import java.io.IOException;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+public class QuotaRatioMappingFactory {
+
+    public static XContentBuilder getMappingContent() {
+        try {
+            return jsonBuilder()
+                .startObject()
+
+                    .startObject(QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE.getValue())
+                        .startObject(PROPERTIES)
+
+                            .startObject(USER)
+                                .field(TYPE, STRING)
+                                .field(INDEX, NOT_ANALYZED)
+                            .endObject()
+
+                            .startObject(DOMAIN)
+                                .field(TYPE, STRING)
+                                .field(INDEX, NOT_ANALYZED)
+                            .endObject()
+
+                            .startObject(QUOTA_RATIO)
+                                .field(TYPE, DOUBLE)
+                            .endObject()
+                        .endObject()
+                    .endObject()
+                .endObject();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaSearchIndexCreationUtil.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaSearchIndexCreationUtil.java
new file mode 100644
index 0000000..f546230
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/QuotaSearchIndexCreationUtil.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import org.apache.james.backends.es.AliasName;
+import org.apache.james.backends.es.ElasticSearchConfiguration;
+import org.apache.james.backends.es.IndexCreationFactory;
+import org.apache.james.backends.es.IndexName;
+import org.apache.james.backends.es.NodeMappingFactory;
+import org.elasticsearch.client.Client;
+
+public class QuotaSearchIndexCreationUtil {
+
+    public static Client prepareClient(Client client,
+                                       AliasName readAlias,
+                                       AliasName writeAlias,
+                                       IndexName indexName,
+                                       ElasticSearchConfiguration configuration) {
+
+        return NodeMappingFactory.applyMapping(
+            new IndexCreationFactory(configuration)
+                .useIndex(indexName)
+                .addAlias(readAlias)
+                .addAlias(writeAlias)
+                .createIndexAndAliases(client),
+            indexName,
+            QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE,
+            QuotaRatioMappingFactory.getMappingContent());
+    }
+
+    public static Client prepareDefaultClient(Client client, ElasticSearchConfiguration configuration) {
+        return prepareClient(client,
+            QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_READ_ALIAS,
+            QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_WRITE_ALIAS,
+            QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_INDEX,
+            configuration);
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListener.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListener.java
new file mode 100644
index 0000000..2b828f2
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListener.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.events;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.james.backends.es.ElasticSearchIndexer;
+import org.apache.james.core.User;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.Group;
+import org.apache.james.mailbox.events.MailboxListener;
+import org.apache.james.quota.search.elasticsearch.QuotaRatioElasticSearchConstants;
+import org.apache.james.quota.search.elasticsearch.json.QuotaRatioToElasticSearchJson;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+public class ElasticSearchQuotaMailboxListener implements MailboxListener.GroupMailboxListener {
+    public static class ElasticSearchQuotaMailboxListenerGroup extends Group {
+    }
+
+    private static final Group GROUP = new ElasticSearchQuotaMailboxListenerGroup();
+
+    private final ElasticSearchIndexer indexer;
+    private final QuotaRatioToElasticSearchJson quotaRatioToElasticSearchJson;
+
+    @Inject
+    public ElasticSearchQuotaMailboxListener(
+        @Named(QuotaRatioElasticSearchConstants.InjectionNames.QUOTA_RATIO) ElasticSearchIndexer indexer,
+        QuotaRatioToElasticSearchJson quotaRatioToElasticSearchJson) {
+        this.indexer = indexer;
+        this.quotaRatioToElasticSearchJson = quotaRatioToElasticSearchJson;
+    }
+
+    @Override
+    public Group getDefaultGroup() {
+        return GROUP;
+    }
+
+    @Override
+    public boolean isHandling(Event event) {
+        return event instanceof QuotaUsageUpdatedEvent;
+    }
+
+    @Override
+    public void event(Event event) throws JsonProcessingException {
+        handleEvent(event.getUser(), (QuotaUsageUpdatedEvent) event);
+    }
+
+    private void handleEvent(User user, QuotaUsageUpdatedEvent event) throws JsonProcessingException {
+        indexer.index(user.asString(),
+            quotaRatioToElasticSearchJson.convertToJson(user.asString(), event));
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/JsonMessageConstants.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/JsonMessageConstants.java
new file mode 100644
index 0000000..8f930ff
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/JsonMessageConstants.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.json;
+
+public interface JsonMessageConstants {
+
+    String USER = "user";
+    String DOMAIN = "domain";
+    String QUOTA_RATIO = "quotaRatio";
+
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJson.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJson.java
new file mode 100644
index 0000000..4084727
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJson.java
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.json;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.james.mailbox.model.QuotaRatio;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class QuotaRatioAsJson {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+
+        private String user;
+        private Optional<String> domain;
+        private QuotaRatio quotaRatio;
+
+        private Builder() {
+            domain = Optional.empty();
+        }
+
+        public Builder user(String user) {
+            this.user = user;
+            return this;
+        }
+
+        public Builder domain(Optional<String> domain) {
+            this.domain = domain;
+            return this;
+        }
+
+        public Builder quotaRatio(QuotaRatio quotaRatio) {
+            this.quotaRatio = quotaRatio;
+            return this;
+        }
+
+        public QuotaRatioAsJson build() {
+            Preconditions.checkState(!Strings.isNullOrEmpty(user), "'user' is mandatory");
+            Preconditions.checkNotNull(quotaRatio, "'quotaRatio' is mandatory");
+
+            return new QuotaRatioAsJson(user, domain, quotaRatio);
+        }
+    }
+
+    private final String user;
+    private final Optional<String> domain;
+    private final QuotaRatio quotaRatio;
+
+    private QuotaRatioAsJson(String user, Optional<String> domain, QuotaRatio quotaRatio) {
+        this.user = user;
+        this.domain = domain;
+        this.quotaRatio = quotaRatio;
+    }
+
+    @JsonProperty(JsonMessageConstants.USER)
+    public String getUser() {
+        return user;
+    }
+
+    @JsonProperty(JsonMessageConstants.DOMAIN)
+    public Optional<String> getDomain() {
+        return domain;
+    }
+
+    @JsonProperty(JsonMessageConstants.QUOTA_RATIO)
+    public double getMaxQuotaRatio() {
+        return quotaRatio.max();
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof QuotaRatioAsJson) {
+            QuotaRatioAsJson that = (QuotaRatioAsJson) o;
+
+            return Objects.equals(this.quotaRatio, that.quotaRatio)
+                && Objects.equals(this.user, that.user)
+                && Objects.equals(this.domain, that.domain);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(user, domain, quotaRatio);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("quotaRatio", quotaRatio)
+            .add("user", user)
+            .add("domain", domain)
+            .toString();
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJson.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJson.java
new file mode 100644
index 0000000..12f9629
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/main/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJson.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.json;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Domain;
+import org.apache.james.mailbox.events.MailboxListener.QuotaUsageUpdatedEvent;
+import org.apache.james.mailbox.model.QuotaRatio;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+public class QuotaRatioToElasticSearchJson {
+
+    private final ObjectMapper mapper;
+
+    @Inject
+    public QuotaRatioToElasticSearchJson() {
+        this.mapper = new ObjectMapper();
+        this.mapper.registerModule(new Jdk8Module());
+    }
+
+    public String convertToJson(String user, QuotaUsageUpdatedEvent event) throws JsonProcessingException {
+        return mapper.writeValueAsString(QuotaRatioAsJson.builder()
+                .user(user)
+                .domain(event.getQuotaRoot().getDomain().map(Domain::asString))
+                .quotaRatio(QuotaRatio.from(event.getSizeQuota(), event.getCountQuota()))
+                .build());
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfigurationTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfigurationTest.java
new file mode 100644
index 0000000..a57a10e
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaConfigurationTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.quota.search.elasticsearch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.james.backends.es.IndexName;
+import org.apache.james.backends.es.ReadAliasName;
+import org.apache.james.backends.es.WriteAliasName;
+import org.junit.Test;
+
+public class ElasticSearchQuotaConfigurationTest {
+
+    @Test
+    public void getReadAliasQuotaRatioNameShouldReturnConfiguredValue() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        String name = "name";
+        configuration.addProperty("elasticsearch.alias.read.quota.ratio.name", name);
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getReadAliasQuotaRatioName())
+            .isEqualTo(new ReadAliasName(name));
+    }
+
+    @Test
+    public void getReadAliasQuotaRatioNameShouldReturnDefaultValueWhenMissing() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getReadAliasQuotaRatioName())
+            .isEqualTo(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_READ_ALIAS);
+    }
+
+    @Test
+    public void getWriteAliasQuotaRatioNameShouldReturnConfiguredValue() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        String name = "name";
+        configuration.addProperty("elasticsearch.alias.write.quota.ratio.name", name);
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getWriteAliasQuotaRatioName())
+            .isEqualTo(new WriteAliasName(name));
+    }
+
+    @Test
+    public void getWriteAliasQuotaRatioNameShouldReturnDefaultValueWhenMissing() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getWriteAliasQuotaRatioName())
+            .isEqualTo(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_WRITE_ALIAS);
+    }
+
+    @Test
+    public void getIndexQuotaRatioNameShouldReturnConfiguredValue() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        String name = "name";
+        configuration.addProperty("elasticsearch.index.quota.ratio.name", name);
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getIndexQuotaRatioName())
+            .isEqualTo(new IndexName(name));
+    }
+
+    @Test
+    public void getIndexQuotaRatioNameShouldReturnDefaultValueWhenMissing() throws ConfigurationException {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+        ElasticSearchQuotaConfiguration elasticSearchConfiguration = ElasticSearchQuotaConfiguration.fromProperties(configuration);
+
+        assertThat(elasticSearchConfiguration.getIndexQuotaRatioName())
+            .isEqualTo(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_INDEX);
+    }
+}
\ No newline at end of file
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java
new file mode 100644
index 0000000..5e63c46
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearchTestSystemExtension.java
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.james.backends.es.DockerElasticSearch;
+import org.apache.james.backends.es.DockerElasticSearchSingleton;
+import org.apache.james.backends.es.ElasticSearchConfiguration;
+import org.apache.james.backends.es.ElasticSearchIndexer;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.store.quota.QuotaComponents;
+import org.apache.james.quota.search.QuotaSearchTestSystem;
+import org.apache.james.quota.search.elasticsearch.events.ElasticSearchQuotaMailboxListener;
+import org.apache.james.quota.search.elasticsearch.json.QuotaRatioToElasticSearchJson;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.james.util.concurrent.NamedThreadFactory;
+import org.elasticsearch.client.Client;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+public class ElasticSearchQuotaSearchTestSystemExtension implements ParameterResolver, BeforeEachCallback, AfterEachCallback {
+
+    private final DockerElasticSearch elasticSearch = DockerElasticSearchSingleton.INSTANCE;
+
+    @Override
+    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+        return (parameterContext.getParameter().getType() == QuotaSearchTestSystem.class);
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+        try {
+            Client client = QuotaSearchIndexCreationUtil.prepareDefaultClient(
+                elasticSearch.clientProvider().get(),
+                ElasticSearchConfiguration.builder()
+                    .addHost(elasticSearch.getTcpHost())
+                    .build());
+
+            InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources();
+
+            MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting();
+
+            DNSService dnsService = mock(DNSService.class);
+            MemoryDomainList domainList = new MemoryDomainList(dnsService);
+            usersRepository.setDomainList(domainList);
+
+            ThreadFactory threadFactory = NamedThreadFactory.withClassName(getClass());
+            ElasticSearchQuotaMailboxListener listener = new ElasticSearchQuotaMailboxListener(
+                new ElasticSearchIndexer(client, Executors.newSingleThreadExecutor(threadFactory),
+                    QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_WRITE_ALIAS,
+                    QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE),
+                new QuotaRatioToElasticSearchJson());
+
+            resources.getMailboxManager().getEventBus().register(listener);
+
+            QuotaComponents quotaComponents = resources.getMailboxManager().getQuotaComponents();
+
+            return new QuotaSearchTestSystem(
+                quotaComponents.getMaxQuotaManager(),
+                resources.getMailboxManager(),
+                quotaComponents.getQuotaManager(),
+                resources.getDefaultUserQuotaRootResolver(),
+                new ElasticSearchQuotaSearcher(client,
+                    QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_READ_ALIAS),
+                usersRepository,
+                domainList,
+                resources.getCurrentQuotaManager(),
+                () -> elasticSearch.awaitForElasticSearch());
+        } catch (Exception e) {
+            throw new ParameterResolutionException("Error while resolving parameter", e);
+        }
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) throws Exception {
+        elasticSearch.start();
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) {
+        elasticSearch.cleanUpData();
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcherTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcherTest.java
new file mode 100644
index 0000000..ad26532
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/ElasticSearchQuotaSearcherTest.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import org.apache.james.quota.search.QuotaSearcherContract;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(ElasticSearchQuotaSearchTestSystemExtension.class)
+public class ElasticSearchQuotaSearcherTest implements QuotaSearcherContract {
+
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverterTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverterTest.java
new file mode 100644
index 0000000..86daa88
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/QuotaQueryConverterTest.java
@@ -0,0 +1,83 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch;
+
+import static org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants.QUOTA_RATIO;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+
+import org.apache.james.core.Domain;
+import org.apache.james.quota.search.QuotaBoundary;
+import org.apache.james.quota.search.QuotaQuery;
+import org.apache.james.quota.search.elasticsearch.json.JsonMessageConstants;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class QuotaQueryConverterTest {
+    private QuotaQueryConverter testee;
+
+    @BeforeEach
+    void setup() {
+        testee = new QuotaQueryConverter();
+    }
+
+    @Test
+    void fromShouldReturnMatchAllWhenEmptyClauses() {
+        QuotaQuery query = QuotaQuery.builder().build();
+        QueryBuilder expected = matchAllQuery();
+
+        QueryBuilder actual = testee.from(query);
+
+        assertThat(actual).isEqualToComparingFieldByField(expected);
+    }
+
+    @Test
+    void fromShouldReturnDomainMatchWhenOnlyDomain() {
+        QuotaQuery query = QuotaQuery.builder().hasDomain(Domain.of("my.tld")).build();
+        QueryBuilder expected = termQuery(JsonMessageConstants.DOMAIN, "my.tld");
+
+        QueryBuilder actual = testee.from(query);
+
+        assertThat(actual).isEqualToComparingFieldByField(expected);
+    }
+
+    @Test
+    void fromShouldReturnQuotaRatioMatchWhenLessThan() {
+        QuotaQuery query = QuotaQuery.builder().lessThan(new QuotaBoundary(0.1)).build();
+        QueryBuilder expected = rangeQuery(QUOTA_RATIO).lte(0.1);
+
+        QueryBuilder actual = testee.from(query);
+
+        assertThat(actual).isEqualToComparingFieldByField(expected);
+    }
+
+    @Test
+    void fromShouldReturnQuotaRatioMatchWhenMoreThan() {
+        QuotaQuery query = QuotaQuery.builder().moreThan(new QuotaBoundary(0.1)).build();
+        QueryBuilder expected = rangeQuery(QUOTA_RATIO).gte(0.1);
+
+        QueryBuilder actual = testee.from(query);
+
+        assertThat(actual).isEqualToComparingFieldByField(expected);
+    }
+
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListenerTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListenerTest.java
new file mode 100644
index 0000000..d3f7811
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/events/ElasticSearchQuotaMailboxListenerTest.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.events;
+
+import static org.apache.james.quota.search.QuotaSearchFixture.TestConstants.BOB_USER;
+import static org.apache.james.quota.search.QuotaSearchFixture.TestConstants.NOW;
+import static org.apache.james.quota.search.QuotaSearchFixture.TestConstants.QUOTAROOT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.james.backends.es.DockerElasticSearchRule;
+import org.apache.james.backends.es.ElasticSearchConfiguration;
+import org.apache.james.backends.es.ElasticSearchIndexer;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.Group;
+import org.apache.james.mailbox.quota.QuotaFixture.Counts;
+import org.apache.james.mailbox.quota.QuotaFixture.Sizes;
+import org.apache.james.mailbox.store.event.EventFactory;
+import org.apache.james.quota.search.elasticsearch.QuotaRatioElasticSearchConstants;
+import org.apache.james.quota.search.elasticsearch.QuotaSearchIndexCreationUtil;
+import org.apache.james.quota.search.elasticsearch.json.QuotaRatioToElasticSearchJson;
+import org.apache.james.util.concurrent.NamedThreadFactory;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.Client;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ElasticSearchQuotaMailboxListenerTest {
+    private static Event.EventId EVENT_ID = Event.EventId.of("6e0dd59d-660e-4d9b-b22f-0354479f47b4");
+
+    private static final int BATCH_SIZE = 1;
+    private static final Event DUMB_EVENT = mock(Event.class);
+
+    @Rule
+    public DockerElasticSearchRule elasticSearch = new DockerElasticSearchRule();
+    private ElasticSearchQuotaMailboxListener quotaMailboxListener;
+    private Client client;
+
+    @Before
+    public void setUp() {
+        client = QuotaSearchIndexCreationUtil.prepareDefaultClient(
+            elasticSearch.clientProvider().get(),
+            ElasticSearchConfiguration.builder()
+                .addHost(elasticSearch.getTcpHost())
+                .build());
+
+        ThreadFactory threadFactory = NamedThreadFactory.withClassName(getClass());
+        quotaMailboxListener = new ElasticSearchQuotaMailboxListener(
+            new ElasticSearchIndexer(client,
+                Executors.newSingleThreadExecutor(threadFactory),
+                QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_WRITE_ALIAS,
+                QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE,
+                BATCH_SIZE),
+            new QuotaRatioToElasticSearchJson());
+    }
+
+    @Test
+    public void deserializeElasticSearchQuotaMailboxListenerGroup() throws Exception {
+        assertThat(Group.deserialize("org.apache.james.quota.search.elasticsearch.events.ElasticSearchQuotaMailboxListener$ElasticSearchQuotaMailboxListenerGroup"))
+            .isEqualTo(new ElasticSearchQuotaMailboxListener.ElasticSearchQuotaMailboxListenerGroup());
+    }
+
+    @Test
+    public void eventShouldIndexEventWhenQuotaEvent() throws Exception {
+        quotaMailboxListener.event(EventFactory.quotaUpdated()
+            .eventId(EVENT_ID)
+            .user(BOB_USER)
+            .quotaRoot(QUOTAROOT)
+            .quotaCount(Counts._52_PERCENT)
+            .quotaSize(Sizes._55_PERCENT)
+            .instant(NOW)
+            .build());
+
+        elasticSearch.awaitForElasticSearch();
+
+        SearchResponse searchResponse = client.prepareSearch(QuotaRatioElasticSearchConstants.DEFAULT_QUOTA_RATIO_READ_ALIAS.getValue())
+            .setTypes(QuotaRatioElasticSearchConstants.QUOTA_RATIO_TYPE.getValue())
+            .setQuery(matchAllQuery())
+            .execute()
+            .get();
+        assertThat(searchResponse.getHits().totalHits()).isEqualTo(1);
+    }
+}
\ No newline at end of file
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJsonTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJsonTest.java
new file mode 100644
index 0000000..b2c6411
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioAsJsonTest.java
@@ -0,0 +1,107 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.json;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Optional;
+
+import org.apache.james.core.quota.QuotaCount;
+import org.apache.james.core.quota.QuotaSize;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.model.QuotaRatio;
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class QuotaRatioAsJsonTest {
+
+    private static final Quota<QuotaSize> QUOTA_SIZE = Quota.<QuotaSize>builder()
+            .used(QuotaSize.size(15))
+            .computedLimit(QuotaSize.size(60))
+            .build();
+    private static final Quota<QuotaCount> QUOTA_COUNT = Quota.<QuotaCount>builder()
+            .used(QuotaCount.count(1))
+            .computedLimit(QuotaCount.count(2))
+            .build();
+
+    @Test
+    public void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(QuotaRatioAsJson.class)
+            .verify();
+    }
+
+    @Test
+    public void buildShouldThrownWhenUserIsNull() {
+        assertThatThrownBy(() -> QuotaRatioAsJson.builder()
+                .build())
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void buildShouldThrownWhenUserIsEmpty() {
+        assertThatThrownBy(() -> QuotaRatioAsJson.builder()
+                .user("")
+                .build())
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void buildShouldThrownWhenQuotaRatioIsNull() {
+        assertThatThrownBy(() -> QuotaRatioAsJson.builder()
+                .user("user")
+                .build())
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void getDomainShouldReturnEmptyWhenNone() {
+        QuotaRatioAsJson quotaRatioAsJson = QuotaRatioAsJson.builder()
+            .user("user")
+            .quotaRatio(QuotaRatio.from(QUOTA_SIZE, QUOTA_COUNT))
+            .build();
+
+        assertThat(quotaRatioAsJson.getDomain()).isEmpty();
+    }
+
+    @Test
+    public void getDomainShouldReturnTheDomainWhenGiven() {
+        String domain = "domain";
+        QuotaRatioAsJson quotaRatioAsJson = QuotaRatioAsJson.builder()
+            .user("user")
+            .domain(Optional.of(domain))
+            .quotaRatio(QuotaRatio.from(QUOTA_SIZE, QUOTA_COUNT))
+            .build();
+
+        assertThat(quotaRatioAsJson.getDomain()).contains(domain);
+    }
+
+    @Test
+    public void getMaxQuotaRatioShouldReturnTheMaxQuotaRatio() {
+        String domain = "domain";
+        QuotaRatioAsJson quotaRatioAsJson = QuotaRatioAsJson.builder()
+            .user("user")
+            .domain(Optional.of(domain))
+            .quotaRatio(QuotaRatio.from(QUOTA_SIZE, QUOTA_COUNT))
+            .build();
+
+        assertThat(quotaRatioAsJson.getMaxQuotaRatio()).isEqualTo(0.5);
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJsonTest.java b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJsonTest.java
new file mode 100644
index 0000000..4f42c92
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/java/org/apache/james/quota/search/elasticsearch/json/QuotaRatioToElasticSearchJsonTest.java
@@ -0,0 +1,80 @@
+/****************************************************************
+ * 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.quota.search.elasticsearch.json;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Optional;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.User;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.MailboxListener.QuotaUsageUpdatedEvent;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mailbox.quota.QuotaFixture;
+import org.apache.james.mailbox.store.event.EventFactory;
+import org.apache.james.util.ClassLoaderUtils;
+import org.junit.jupiter.api.Test;
+
+class QuotaRatioToElasticSearchJsonTest {
+    private static Event.EventId EVENT_ID = Event.EventId.of("6e0dd59d-660e-4d9b-b22f-0354479f47b4");
+
+    @Test
+    void quotaRatioShouldBeWellConvertedToJson() throws IOException {
+        String user = "user@domain.org";
+        QuotaUsageUpdatedEvent event = EventFactory.quotaUpdated()
+            .eventId(EVENT_ID)
+            .user(User.fromUsername(user))
+            .quotaRoot(QuotaRoot.quotaRoot(user, Optional.of(Domain.of("domain.org"))))
+            .quotaCount(QuotaFixture.Counts._52_PERCENT)
+            .quotaSize(QuotaFixture.Sizes._55_PERCENT)
+            .instant(Instant.now())
+            .build();
+        QuotaRatioToElasticSearchJson quotaRatioToElasticSearchJson = new QuotaRatioToElasticSearchJson();
+        String convertToJson = quotaRatioToElasticSearchJson.convertToJson(user, event);
+
+        assertThatJson(convertToJson)
+            .when(IGNORING_ARRAY_ORDER)
+            .isEqualTo(ClassLoaderUtils.getSystemResourceAsString("quotaRatio.json"));
+    }
+
+    @Test
+    void quotaRatioShouldBeWellConvertedToJsonWhenNoDomain() throws IOException {
+        String user = "user";
+        QuotaUsageUpdatedEvent event = EventFactory.quotaUpdated()
+            .eventId(EVENT_ID)
+            .user(User.fromUsername(user))
+            .quotaRoot(QuotaRoot.quotaRoot(user, Optional.empty()))
+            .quotaCount(QuotaFixture.Counts._52_PERCENT)
+            .quotaSize(QuotaFixture.Sizes._55_PERCENT)
+            .instant(Instant.now())
+            .build();
+
+
+        QuotaRatioToElasticSearchJson quotaRatioToElasticSearchJson = new QuotaRatioToElasticSearchJson();
+        String convertToJson = quotaRatioToElasticSearchJson.convertToJson(user, event);
+
+        assertThatJson(convertToJson)
+            .when(IGNORING_ARRAY_ORDER)
+            .isEqualTo(ClassLoaderUtils.getSystemResourceAsString("quotaRatioNoDomain.json"));
+    }
+}
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatio.json b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatio.json
new file mode 100644
index 0000000..8b39489
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatio.json
@@ -0,0 +1 @@
+{"user":"user@domain.org","domain":"domain.org","quotaRatio":0.55}
\ No newline at end of file
diff --git a/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatioNoDomain.json b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatioNoDomain.json
new file mode 100644
index 0000000..b3e2f2e
--- /dev/null
+++ b/mailbox/plugin/quota-search-elasticsearch-v6/src/test/resources/quotaRatioNoDomain.json
@@ -0,0 +1 @@
+{"user":"user","domain":null,"quotaRatio":0.55}
\ No newline at end of file
diff --git a/mailbox/pom.xml b/mailbox/pom.xml
index 7e3dcd2..449bb35 100644
--- a/mailbox/pom.xml
+++ b/mailbox/pom.xml
@@ -57,6 +57,7 @@
         <module>plugin/quota-mailing-memory</module>
         <module>plugin/quota-search</module>
         <module>plugin/quota-search-elasticsearch</module>
+        <module>plugin/quota-search-elasticsearch-v6</module>
         <module>plugin/quota-search-scanning</module>
         <module>plugin/spamassassin</module>
 


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