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