Author: btellier
Date: Mon Jun 29 08:28:36 2015
New Revision: 1688120
URL: http://svn.apache.org/r1688120
Log:
MAILBOX-235 Conversions between James queries and ElasticSearch queries - many
thanks to Matthieu Baechlor and Antoine Duprat for their reviews
Added:
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/CriterionConverter.java
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollector.java
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryRepresentation.java
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/QueryConverter.java
james/mailbox/trunk/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollectorTest.java
Modified:
james/mailbox/trunk/elasticsearch/pom.xml
Modified: james/mailbox/trunk/elasticsearch/pom.xml
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/pom.xml?rev=1688120&r1=1688119&r2=1688120&view=diff
==============================================================================
--- james/mailbox/trunk/elasticsearch/pom.xml (original)
+++ james/mailbox/trunk/elasticsearch/pom.xml Mon Jun 29 08:28:36 2015
@@ -95,6 +95,7 @@
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
+ <version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
Added:
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/CriterionConverter.java
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/CriterionConverter.java?rev=1688120&view=auto
==============================================================================
---
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/CriterionConverter.java
(added)
+++
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/CriterionConverter.java
Mon Jun 29 08:28:36 2015
@@ -0,0 +1,246 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.query;
+
+import org.apache.james.mailbox.elasticsearch.json.HeaderCollection;
+import org.apache.james.mailbox.elasticsearch.json.JsonMessageConstants;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.SearchQuery.Criterion;
+import org.apache.james.mailbox.model.SearchQuery.HeaderOperator;
+
+import javax.mail.Flags;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import static org.elasticsearch.index.query.FilterBuilders.existsFilter;
+import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
+import static org.elasticsearch.index.query.FilterBuilders.termFilter;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+public class CriterionConverter {
+
+ private Map<Class<?>, Function<SearchQuery.Criterion,
FilteredQueryRepresentation>> criterionConverterMap;
+ private Map<Class<?>, BiFunction<String, SearchQuery.HeaderOperator,
FilteredQueryRepresentation>> headerOperatorConverterMap;
+
+ public CriterionConverter() {
+ criterionConverterMap = new HashMap<>();
+ headerOperatorConverterMap = new HashMap<>();
+
+ registerCriterionConverters();
+ registerHeaderOperatorConverters();
+ }
+
+ private void registerCriterionConverters() {
+ registerCriterionConverter(SearchQuery.FlagCriterion.class,
this::convertFlag);
+ registerCriterionConverter(SearchQuery.UidCriterion.class,
this::convertUid);
+ registerCriterionConverter(SearchQuery.ConjunctionCriterion.class,
this::convertConjunction);
+ registerCriterionConverter(SearchQuery.InternalDateCriterion.class,
this::convertInternalDate);
+ registerCriterionConverter(SearchQuery.HeaderCriterion.class,
this::convertHeader);
+
+ registerCriterionConverter(
+ SearchQuery.AllCriterion.class,
+ criterion ->
FilteredQueryRepresentation.fromQuery(matchAllQuery()));
+
+ registerCriterionConverter(
+ SearchQuery.TextCriterion.class,
+ criterion -> FilteredQueryRepresentation.fromQuery(
+ matchQuery(JsonMessageConstants.TEXT_BODY,
criterion.getOperator().getValue())));
+
+ registerCriterionConverter(
+ SearchQuery.ModSeqCriterion.class,
+ criterion -> createNumericFilter(JsonMessageConstants.MODSEQ,
criterion.getOperator()));
+
+ registerCriterionConverter(
+ SearchQuery.SizeCriterion.class,
+ criterion -> createNumericFilter(JsonMessageConstants.SIZE,
criterion.getOperator()));
+
+ registerCriterionConverter(
+ SearchQuery.CustomFlagCriterion.class,
+ criterion -> FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.USER_FLAGS,
criterion.getFlag())));
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends Criterion> void registerCriterionConverter(Class<T>
type, Function<T, FilteredQueryRepresentation> f) {
+ criterionConverterMap.put(type, (Function<Criterion,
FilteredQueryRepresentation>) f);
+ }
+
+ private void registerHeaderOperatorConverters() {
+
+ registerHeaderOperatorConverter(
+ SearchQuery.ExistsOperator.class,
+ (headerName, operator) -> FilteredQueryRepresentation.fromFilter(
+ existsFilter(JsonMessageConstants.HEADERS + "." + headerName))
+ );
+
+ registerHeaderOperatorConverter(
+ SearchQuery.AddressOperator.class,
+ (headerName, operator) -> manageAddressFields(headerName,
operator.getAddress()));
+
+ registerHeaderOperatorConverter(
+ SearchQuery.DateOperator.class,
+ (headerName, operator) ->
dateRangeFilter(JsonMessageConstants.SENT_DATE, operator));
+
+ registerHeaderOperatorConverter(
+ SearchQuery.ContainsOperator.class,
+ (headerName, operator) -> FilteredQueryRepresentation.fromQuery(
+ matchQuery(JsonMessageConstants.HEADERS + "." + headerName,
+ operator.getValue())));
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends HeaderOperator> void
registerHeaderOperatorConverter(Class<T> type, BiFunction<String, T,
FilteredQueryRepresentation> f) {
+ headerOperatorConverterMap.put(type, (BiFunction<String,
HeaderOperator, FilteredQueryRepresentation>) f);
+ }
+
+ public FilteredQueryRepresentation convertCriterion(SearchQuery.Criterion
criterion) {
+ return
criterionConverterMap.get(criterion.getClass()).apply(criterion);
+ }
+
+ private FilteredQueryRepresentation
convertInternalDate(SearchQuery.InternalDateCriterion dateCriterion) {
+ SearchQuery.DateOperator dateOperator = dateCriterion.getOperator();
+ return dateRangeFilter(JsonMessageConstants.DATE, dateOperator);
+ }
+
+ private FilteredQueryRepresentation dateRangeFilter(String field,
SearchQuery.DateOperator dateOperator) {
+ SearchQuery.DateResolution dateResolution =
dateOperator.getDateResultion();
+ String lowDateString =
ISO_OFFSET_DATE_TIME.format(DateResolutionFormater.computeLowerDate(DateResolutionFormater.convertDateToZonedDateTime(dateOperator.getDate()),
dateResolution));
+ String upDateString = ISO_OFFSET_DATE_TIME.format(
+ DateResolutionFormater.computeUpperDate(
+
DateResolutionFormater.convertDateToZonedDateTime(dateOperator.getDate()),
+ dateResolution));
+ return convertDateOperatorToFiteredQuery(field, dateOperator,
lowDateString, upDateString);
+ }
+
+ private FilteredQueryRepresentation
convertConjunction(SearchQuery.ConjunctionCriterion criterion) {
+ return criterion.getCriteria().stream()
+ .map(this::convertCriterion)
+ .collect(new FilteredQueryCollector(criterion.getType()));
+ }
+
+ private FilteredQueryRepresentation convertFlag(SearchQuery.FlagCriterion
flagCriterion) {
+ SearchQuery.BooleanOperator operator = flagCriterion.getOperator();
+ Flags.Flag flag = flagCriterion.getFlag();
+ if (flag.equals(Flags.Flag.DELETED) ) {
+ return
FilteredQueryRepresentation.fromFilter(termFilter(JsonMessageConstants.IS_DELETED,
operator.isSet()));
+ }
+ if (flag.equals(Flags.Flag.ANSWERED) ) {
+ return FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.IS_ANSWERED,
operator.isSet()));
+ }
+ if (flag.equals(Flags.Flag.DRAFT) ) {
+ return FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.IS_DRAFT, operator.isSet()));
+ }
+ if (flag.equals(Flags.Flag.SEEN) ) {
+ return FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.IS_UNREAD, !operator.isSet()));
+ }
+ if (flag.equals(Flags.Flag.RECENT) ) {
+ return FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.IS_RECENT, operator.isSet()));
+ }
+ if (flag.equals(Flags.Flag.FLAGGED) ) {
+ return FilteredQueryRepresentation.fromFilter(
+ termFilter(JsonMessageConstants.IS_FLAGGED, operator.isSet()));
+ }
+ throw new RuntimeException("Unknown flag used in Flag search
criterion");
+ }
+
+ private FilteredQueryRepresentation createNumericFilter(String fieldName,
SearchQuery.NumericOperator operator) {
+ switch (operator.getType()) {
+ case EQUALS:
+ return FilteredQueryRepresentation.fromFilter(
+
rangeFilter(fieldName).gte(operator.getValue()).lte(operator.getValue()));
+ case GREATER_THAN:
+ return
FilteredQueryRepresentation.fromFilter(rangeFilter(fieldName).gte(operator.getValue()));
+ case LESS_THAN:
+ return
FilteredQueryRepresentation.fromFilter(rangeFilter(fieldName).lte(operator.getValue()));
+ default:
+ throw new RuntimeException("A non existing numeric operator was
triggered");
+ }
+ }
+
+ private FilteredQueryRepresentation convertUid(SearchQuery.UidCriterion
uidCriterion) {
+ if (uidCriterion.getOperator().getRange().length == 0) {
+ return FilteredQueryRepresentation.empty();
+ }
+ return Arrays.stream(uidCriterion.getOperator().getRange())
+ .map(this::uidRangeFilter)
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.OR));
+ }
+
+ private FilteredQueryRepresentation
uidRangeFilter(SearchQuery.NumericRange numericRange) {
+ return FilteredQueryRepresentation.fromFilter(
+ rangeFilter(JsonMessageConstants.ID)
+ .lte(numericRange.getHighValue())
+ .gte(numericRange.getLowValue()));
+ }
+
+ private FilteredQueryRepresentation
convertHeader(SearchQuery.HeaderCriterion headerCriterion) {
+ return
headerOperatorConverterMap.get(headerCriterion.getOperator().getClass())
+ .apply(
+ headerCriterion.getHeaderName().toLowerCase(),
+ headerCriterion.getOperator());
+ }
+
+ private FilteredQueryRepresentation manageAddressFields(String headerName,
String value) {
+ return FilteredQueryRepresentation.fromQuery(
+ nestedQuery(getFieldNameFromHeaderName(headerName),
boolQuery().should(matchQuery(getFieldNameFromHeaderName(headerName) + "." +
JsonMessageConstants.EMailer.NAME,
value)).should(matchQuery(getFieldNameFromHeaderName(headerName) + "." +
JsonMessageConstants.EMailer.ADDRESS, value))));
+ }
+
+ private String getFieldNameFromHeaderName(String headerName) {
+ switch (headerName.toLowerCase()) {
+ case HeaderCollection.TO:
+ return JsonMessageConstants.TO;
+ case HeaderCollection.CC:
+ return JsonMessageConstants.CC;
+ case HeaderCollection.BCC:
+ return JsonMessageConstants.BCC;
+ case HeaderCollection.FROM:
+ return JsonMessageConstants.FROM;
+ }
+ throw new RuntimeException("Header not recognized as Addess Header : "
+ headerName);
+ }
+
+ private FilteredQueryRepresentation
convertDateOperatorToFiteredQuery(String field, SearchQuery.DateOperator
dateOperator, String lowDateString, String upDateString) {
+ switch (dateOperator.getType()) {
+ case BEFORE:
+ return FilteredQueryRepresentation.fromFilter(
+ rangeFilter(field).lte(upDateString));
+ case AFTER:
+ return FilteredQueryRepresentation.fromFilter(
+ rangeFilter(field).gte(lowDateString));
+ case ON:
+ return FilteredQueryRepresentation.fromFilter(
+ rangeFilter(field).lte(upDateString).gte(lowDateString));
+ }
+ throw new RuntimeException("Unknown date operator");
+ }
+
+}
Added:
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollector.java
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollector.java?rev=1688120&view=auto
==============================================================================
---
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollector.java
(added)
+++
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollector.java
Mon Jun 29 08:28:36 2015
@@ -0,0 +1,123 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.query;
+
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.FilterBuilders.boolFilter;
+
+import org.apache.james.mailbox.model.SearchQuery;
+import org.elasticsearch.index.query.BoolFilterBuilder;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+
+import java.util.EnumSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+public class FilteredQueryCollector implements
Collector<FilteredQueryRepresentation, FilteredQueryRepresentation,
FilteredQueryRepresentation> {
+
+ private final SearchQuery.Conjunction type;
+
+ public FilteredQueryCollector(SearchQuery.Conjunction type) {
+ this.type = type;
+ }
+
+ @Override
+ public Supplier<FilteredQueryRepresentation> supplier() {
+ return FilteredQueryRepresentation::empty;
+ }
+
+ @Override
+ public BiConsumer<FilteredQueryRepresentation,
FilteredQueryRepresentation> accumulator() {
+ return this::apply;
+ }
+
+ @Override
+ public BinaryOperator<FilteredQueryRepresentation> combiner() {
+ return this::apply;
+ }
+
+ @Override
+ public Function<FilteredQueryRepresentation, FilteredQueryRepresentation>
finisher() {
+ return (accumulator)->accumulator;
+ }
+
+ @Override
+ public Set<Characteristics> characteristics() {
+ return EnumSet.of(Characteristics.UNORDERED);
+ }
+
+ private FilteredQueryRepresentation apply(FilteredQueryRepresentation
accumulator, FilteredQueryRepresentation collected) {
+ initializeAccumulatorWhenNeeded(accumulator, collected);
+ switch (type) {
+ case OR:
+ return applyOr(accumulator, collected);
+ case AND:
+ return applyAnd(accumulator, collected);
+ case NOR:
+ return applyNor(accumulator, collected);
+ }
+ return accumulator;
+ }
+
+ private void initializeAccumulatorWhenNeeded(FilteredQueryRepresentation
accumulator, FilteredQueryRepresentation collected) {
+ // This method is compulsory because elasticSearch refuses to build
empty BoolQuery and empty BoolFilter
+ if (!accumulator.getQuery().isPresent() &&
collected.getQuery().isPresent()) {
+ accumulator.setQuery(Optional.of(boolQuery()));
+ }
+ if (!accumulator.getFilter().isPresent() &&
collected.getFilter().isPresent()) {
+ accumulator.setFilter(Optional.of(boolFilter()));
+ }
+ }
+
+ private FilteredQueryRepresentation applyNor(FilteredQueryRepresentation
accumulator, FilteredQueryRepresentation collected) {
+ if (collected.getQuery().isPresent()) {
+ ((BoolQueryBuilder)
accumulator.getQuery().get()).mustNot(collected.getQuery().get());
+ }
+ if (collected.getFilter().isPresent()) {
+ ((BoolFilterBuilder)
accumulator.getFilter().get()).mustNot(collected.getFilter().get());
+ }
+ return accumulator;
+ }
+
+ private FilteredQueryRepresentation applyAnd(FilteredQueryRepresentation
accumulator, FilteredQueryRepresentation collected) {
+ if (collected.getQuery().isPresent()) {
+ ((BoolQueryBuilder)
accumulator.getQuery().get()).must(collected.getQuery().get());
+ }
+ if (collected.getFilter().isPresent()) {
+ ((BoolFilterBuilder)
accumulator.getFilter().get()).must(collected.getFilter().get());
+ }
+ return accumulator;
+ }
+
+ private FilteredQueryRepresentation applyOr(FilteredQueryRepresentation
accumulator, FilteredQueryRepresentation collected) {
+ if (collected.getQuery().isPresent()) {
+ ((BoolQueryBuilder)
accumulator.getQuery().get()).should(collected.getQuery().get());
+ }
+ if (collected.getFilter().isPresent()) {
+ ((BoolFilterBuilder)
accumulator.getFilter().get()).should(collected.getFilter().get());
+ }
+ return accumulator;
+ }
+}
Added:
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryRepresentation.java
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryRepresentation.java?rev=1688120&view=auto
==============================================================================
---
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryRepresentation.java
(added)
+++
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryRepresentation.java
Mon Jun 29 08:28:36 2015
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.query;
+
+import org.elasticsearch.index.query.FilterBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+
+import java.util.Optional;
+
+public class FilteredQueryRepresentation {
+
+ public static FilteredQueryRepresentation fromQuery(QueryBuilder query) {
+ return new FilteredQueryRepresentation(Optional.of(query),
Optional.empty());
+ }
+
+ public static FilteredQueryRepresentation fromFilter(FilterBuilder filter)
{
+ return new FilteredQueryRepresentation(Optional.empty(),
Optional.of(filter));
+ }
+
+ public static FilteredQueryRepresentation empty() {
+ return new FilteredQueryRepresentation(Optional.empty(),
Optional.empty());
+ }
+
+ private Optional<FilterBuilder> filter;
+ private Optional<QueryBuilder> query;
+
+ private FilteredQueryRepresentation(Optional<QueryBuilder> query,
Optional<FilterBuilder> filter) {
+ this.query = query;
+ this.filter = filter;
+ }
+
+ public Optional<FilterBuilder> getFilter() {
+ return filter;
+ }
+
+ public Optional<QueryBuilder> getQuery() {
+ return query;
+ }
+
+ public void setFilter(Optional<FilterBuilder> filter) {
+ this.filter = filter;
+ }
+
+ public void setQuery(Optional<QueryBuilder> query) {
+ this.query = query;
+ }
+}
Added:
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/QueryConverter.java
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/QueryConverter.java?rev=1688120&view=auto
==============================================================================
---
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/QueryConverter.java
(added)
+++
james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/query/QueryConverter.java
Mon Jun 29 08:28:36 2015
@@ -0,0 +1,81 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.query;
+
+import org.apache.james.mailbox.elasticsearch.json.JsonMessageConstants;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.elasticsearch.common.lang3.tuple.Pair;
+import org.elasticsearch.index.query.QueryBuilder;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.index.query.FilterBuilders.termFilter;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
+
+public class QueryConverter implements Function<Pair<SearchQuery, String>,
QueryBuilder> {
+
+
+ private final CriterionConverter criterionConverter;
+
+ public QueryConverter(CriterionConverter criterionConverter) {
+ this.criterionConverter = criterionConverter;
+ }
+
+ @Override
+ public QueryBuilder apply(Pair<SearchQuery, String> pair) {
+ return from(pair.getLeft(), pair.getRight());
+ }
+
+ public QueryBuilder from(SearchQuery searchQuery, String mailboxUUID) {
+ return Stream.of(generateQueryBuilder(searchQuery))
+ .map((rep) -> addMailboxFilters(rep, mailboxUUID))
+ .map(this::getFinalQuery)
+ .findAny()
+ .get();
+ }
+
+ private FilteredQueryRepresentation generateQueryBuilder(SearchQuery
searchQuery) {
+ List<SearchQuery.Criterion> criteria = searchQuery.getCriterias();
+ if (criteria.isEmpty()) {
+ return criterionConverter.convertCriterion(SearchQuery.all());
+ } else if (criteria.size() == 1) {
+ return criterionConverter.convertCriterion(criteria.get(0));
+ } else {
+ return criterionConverter.convertCriterion(new
SearchQuery.ConjunctionCriterion(SearchQuery.Conjunction.AND, criteria));
+ }
+ }
+
+ private FilteredQueryRepresentation
addMailboxFilters(FilteredQueryRepresentation elasticsearchQueryRepresentation,
String mailboxUUID) {
+ return Stream.of(elasticsearchQueryRepresentation,
+
FilteredQueryRepresentation.fromFilter(termFilter(JsonMessageConstants.MAILBOX_ID,
mailboxUUID)))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ }
+
+ private QueryBuilder getFinalQuery(FilteredQueryRepresentation
filteredQueryRepresentation) {
+ QueryBuilder query =
filteredQueryRepresentation.getQuery().orElse(matchAllQuery());
+ if (!filteredQueryRepresentation.getFilter().isPresent()) {
+ return query;
+ }
+ return filteredQuery(query,
filteredQueryRepresentation.getFilter().get());
+ }
+
+}
Added:
james/mailbox/trunk/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollectorTest.java
URL:
http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollectorTest.java?rev=1688120&view=auto
==============================================================================
---
james/mailbox/trunk/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollectorTest.java
(added)
+++
james/mailbox/trunk/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/query/FilteredQueryCollectorTest.java
Mon Jun 29 08:28:36 2015
@@ -0,0 +1,106 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.query;
+
+import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.index.query.FilterBuilders.termFilter;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+
+import com.google.common.collect.Lists;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+public class FilteredQueryCollectorTest {
+
+ @Test
+ public void
emptyStreamShouldBeCollectedAsEmptyFilteredQueryRepresentation() throws
Exception {
+ List<FilteredQueryRepresentation> emptyFilteredQueryRepresentationList
= Lists.newArrayList();
+ FilteredQueryRepresentation collectionResult =
emptyFilteredQueryRepresentationList
+ .stream()
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isEmpty();
+ assertThat(collectionResult.getQuery()).isEmpty();
+ }
+
+ @Test
+ public void queryAloneShouldBeWellCollected() throws Exception {
+ FilteredQueryRepresentation collectionResult =
Stream.of(FilteredQueryRepresentation.fromQuery(matchAllQuery()))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isEmpty();
+ assertThat(collectionResult.getQuery()).isPresent();
+
assertThatJson(collectionResult.getQuery().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+ .isEqualTo("{\"bool\":{\"must\":{\"match_all\":{}}}}");
+ }
+
+ @Test
+ public void filterAloneShouldBeWellCollected() throws Exception {
+ FilteredQueryRepresentation collectionResult =
Stream.of(FilteredQueryRepresentation.fromFilter(termFilter("field", "value")))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isPresent();
+ assertThat(collectionResult.getQuery()).isEmpty();
+
assertThatJson(collectionResult.getFilter().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+
.isEqualTo("{\"bool\":{\"must\":{\"term\":{\"field\":\"value\"}}}}");
+ }
+
+ @Test
+ public void aggregationBetweenQueryAndFilterShouldWork() throws Exception {
+ FilteredQueryRepresentation collectionResult = Stream.of(
+ FilteredQueryRepresentation.fromFilter(termFilter("field",
"value")),
+ FilteredQueryRepresentation.fromQuery(matchAllQuery()))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isPresent();
+ assertThat(collectionResult.getQuery()).isPresent();
+
assertThatJson(collectionResult.getQuery().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+ .isEqualTo("{\"bool\":{\"must\":{\"match_all\":{}}}}");
+
assertThatJson(collectionResult.getFilter().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+
.isEqualTo("{\"bool\":{\"must\":{\"term\":{\"field\":\"value\"}}}}");
+ }
+
+ @Test
+ public void queryAggregationShouldWork() throws Exception {
+ FilteredQueryRepresentation collectionResult = Stream.of(
+ FilteredQueryRepresentation.fromQuery(matchAllQuery()),
+ FilteredQueryRepresentation.fromQuery(matchAllQuery()))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isEmpty();
+ assertThat(collectionResult.getQuery()).isPresent();
+
assertThatJson(collectionResult.getQuery().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+
.isEqualTo("{\"bool\":{\"must\":[{\"match_all\":{}},{\"match_all\":{}}]}}");
+ }
+
+ @Test
+ public void filterAggregationShouldWork() throws Exception {
+ FilteredQueryRepresentation collectionResult = Stream.of(
+ FilteredQueryRepresentation.fromFilter(termFilter("field",
"value")),
+ FilteredQueryRepresentation.fromFilter(termFilter("field",
"value")))
+ .collect(new FilteredQueryCollector(SearchQuery.Conjunction.AND));
+ assertThat(collectionResult.getFilter()).isPresent();
+ assertThat(collectionResult.getQuery()).isEmpty();
+
assertThatJson(collectionResult.getFilter().get().toXContent(jsonBuilder(),
QueryBuilder.EMPTY_PARAMS).string())
+
.isEqualTo("{\"bool\":{\"must\":[{\"term\":{\"field\":\"value\"}},{\"term\":{\"field\":\"value\"}}]}}");
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]