org.hibernate.search.backend.elasticsearch.impl.ToElasticsearch.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.search.backend.elasticsearch.impl.ToElasticsearch.java

Source

/*
 * Hibernate Search, full-text search for your domain model
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.search.backend.elasticsearch.impl;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.hibernate.search.analyzer.impl.LuceneAnalyzerReference;
import org.hibernate.search.backend.elasticsearch.logging.impl.Log;
import org.hibernate.search.backend.spi.DeletionQuery;
import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity;
import org.hibernate.search.query.dsl.impl.DiscreteFacetRequest;
import org.hibernate.search.query.dsl.impl.FacetRange;
import org.hibernate.search.query.dsl.impl.RangeFacetRequest;
import org.hibernate.search.query.facet.FacetSortOrder;
import org.hibernate.search.query.facet.FacetingRequest;
import org.hibernate.search.spatial.impl.DistanceFilter;
import org.hibernate.search.spatial.impl.SpatialHashFilter;
import org.hibernate.search.util.impl.ScopedAnalyzer;
import org.hibernate.search.util.logging.impl.LoggerFactory;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

/**
 * Various utilities to transform Hibernate Search API into Elasticsearch JSON.
 *
 * @author Guillaume Smet
 * @author Gunnar Morling
 */
public class ToElasticsearch {

    private static final Log LOG = LoggerFactory.make(Log.class);

    private ToElasticsearch() {
    }

    public static void addFacetingRequest(JsonBuilder.Object jsonQuery, FacetingRequest facetingRequest) {
        String fieldName = facetingRequest.getFieldName();
        if (facetingRequest instanceof DiscreteFacetRequest) {
            JsonObject termsJsonQuery = JsonBuilder.object()
                    .add("terms",
                            JsonBuilder.object().addProperty("field", fieldName)
                                    .addProperty("size",
                                            facetingRequest.getMaxNumberOfFacets() == -1 ? 0
                                                    : facetingRequest.getMaxNumberOfFacets())
                                    .add("order", fromFacetSortOrder(facetingRequest.getSort()))
                                    .addProperty("min_doc_count", facetingRequest.hasZeroCountsIncluded() ? 0 : 1))
                    .build();

            if (isNested(fieldName)) {
                JsonBuilder.Object facetJsonQuery = JsonBuilder.object();
                facetJsonQuery.add("nested",
                        JsonBuilder.object().addProperty("path", FieldHelper.getEmbeddedFieldPath(fieldName)));
                facetJsonQuery.add("aggregations",
                        JsonBuilder.object().add(facetingRequest.getFacetingName(), termsJsonQuery));
                jsonQuery.add(facetingRequest.getFacetingName(), facetJsonQuery);
            } else {
                jsonQuery.add(facetingRequest.getFacetingName(), termsJsonQuery);
            }
        } else if (facetingRequest instanceof RangeFacetRequest<?>) {
            RangeFacetRequest<?> rangeFacetingRequest = (RangeFacetRequest<?>) facetingRequest;
            for (FacetRange<?> facetRange : rangeFacetingRequest.getFacetRangeList()) {
                JsonBuilder.Object comparisonFragment = JsonBuilder.object();
                if (facetRange.getMin() != null) {
                    comparisonFragment.addProperty(facetRange.isMinIncluded() ? "gte" : "gt", facetRange.getMin());
                }
                if (facetRange.getMax() != null) {
                    comparisonFragment.addProperty(facetRange.isMaxIncluded() ? "lte" : "lt", facetRange.getMax());
                }

                JsonObject rangeQuery = wrapQueryForNestedIfRequired(fieldName, JsonBuilder.object()
                        .add("range", JsonBuilder.object().add(fieldName, comparisonFragment)).build());

                jsonQuery.add(facetingRequest.getFacetingName() + "-" + facetRange.getIdentifier(),
                        JsonBuilder.object().add("filter", rangeQuery));
            }
        } else {
            throw new IllegalArgumentException(
                    "Faceting request of type " + facetingRequest.getClass().getName() + " not supported");
        }
    }

    private static JsonObject fromFacetSortOrder(FacetSortOrder sortOrder) {
        JsonObject sort = new JsonObject();
        switch (sortOrder) {
        case COUNT_ASC:
            sort.addProperty("_count", "asc");
            break;
        case COUNT_DESC:
            sort.addProperty("_count", "desc");
            break;
        case FIELD_VALUE:
            sort.addProperty("_term", "asc");
            break;
        case RANGE_DEFINITION_ORDER:
            throw LOG.cannotSendRangeDefinitionOrderToElasticsearchBackend();
        }
        return sort;
    }

    public static JsonObject condition(String operator, JsonArray conditions) {
        JsonObject jsonCondition;
        if (conditions.size() == 1) {
            jsonCondition = conditions.get(0).getAsJsonObject();
        } else {
            jsonCondition = JsonBuilder.object().add("bool", JsonBuilder.object().add(operator, conditions))
                    .build();
        }
        return jsonCondition;
    }

    public static JsonObject fromLuceneQuery(Query query) {
        if (query instanceof MatchAllDocsQuery) {
            return convertMatchAllDocsQuery((MatchAllDocsQuery) query);
        } else if (query instanceof TermQuery) {
            return convertTermQuery((TermQuery) query);
        } else if (query instanceof BooleanQuery) {
            return convertBooleanQuery((BooleanQuery) query);
        } else if (query instanceof TermRangeQuery) {
            return convertTermRangeQuery((TermRangeQuery) query);
        } else if (query instanceof NumericRangeQuery) {
            return convertNumericRangeQuery((NumericRangeQuery<?>) query);
        } else if (query instanceof WildcardQuery) {
            return convertWildcardQuery((WildcardQuery) query);
        } else if (query instanceof FuzzyQuery) {
            return convertFuzzyQuery((FuzzyQuery) query);
        } else if (query instanceof PhraseQuery) {
            return convertPhraseQuery((PhraseQuery) query);
        } else if (query instanceof ConstantScoreQuery) {
            return convertConstantScoreQuery((ConstantScoreQuery) query);
        } else if (query instanceof FilteredQuery) {
            return convertFilteredQuery((FilteredQuery) query);
        } else if (query instanceof Filter) {
            return fromLuceneFilter((Filter) query);
        }

        throw LOG.cannotTransformLuceneQueryIntoEsQuery(query);
    }

    public static JsonObject fromDeletionQuery(DocumentBuilderIndexedEntity metadata, DeletionQuery deletionQuery) {
        ScopedAnalyzer scopedAnalyzer = (ScopedAnalyzer) metadata.getAnalyzer()
                .unwrap(LuceneAnalyzerReference.class).getAnalyzer();
        return fromLuceneQuery(deletionQuery.toLuceneQuery(scopedAnalyzer));
    }

    private static JsonObject convertMatchAllDocsQuery(MatchAllDocsQuery matchAllDocsQuery) {
        return JsonBuilder.object().add("match_all", new JsonObject()).build();
    }

    private static JsonObject convertBooleanQuery(BooleanQuery booleanQuery) {
        JsonArray musts = new JsonArray();
        JsonArray shoulds = new JsonArray();
        JsonArray mustNots = new JsonArray();
        JsonArray filters = new JsonArray();

        for (BooleanClause clause : booleanQuery.clauses()) {
            switch (clause.getOccur()) {
            case MUST:
                musts.add(fromLuceneQuery(clause.getQuery()));
                break;
            case FILTER:
                filters.add(fromLuceneQuery(clause.getQuery()));
                break;
            case MUST_NOT:
                mustNots.add(fromLuceneQuery(clause.getQuery()));
                break;
            case SHOULD:
                shoulds.add(fromLuceneQuery(clause.getQuery()));
                break;
            }
        }

        JsonObject clauses = new JsonObject();

        if (musts.size() > 1) {
            clauses.add("must", musts);
        } else if (musts.size() == 1) {
            clauses.add("must", musts.iterator().next());
        }

        if (shoulds.size() > 1) {
            clauses.add("should", shoulds);
        } else if (shoulds.size() == 1) {
            clauses.add("should", shoulds.iterator().next());
        }

        if (mustNots.size() > 1) {
            clauses.add("must_not", mustNots);
        } else if (mustNots.size() == 1) {
            clauses.add("must_not", mustNots.iterator().next());
        }

        if (filters.size() > 1) {
            clauses.add("filter", filters);
        } else if (filters.size() == 1) {
            clauses.add("filter", filters.iterator().next());
        }

        JsonObject bool = new JsonObject();
        bool.add("bool", clauses);
        return bool;
    }

    private static JsonObject convertTermQuery(TermQuery termQuery) {
        String field = termQuery.getTerm().field();

        JsonObject matchQuery = JsonBuilder.object()
                .add("term",
                        JsonBuilder.object().add(field,
                                JsonBuilder.object().addProperty("value", termQuery.getTerm().text())
                                        .addProperty("boost", termQuery.getBoost())))
                .build();

        return wrapQueryForNestedIfRequired(field, matchQuery);
    }

    private static JsonObject convertWildcardQuery(WildcardQuery query) {
        String field = query.getTerm().field();

        JsonObject wildcardQuery = JsonBuilder.object()
                .add("wildcard", JsonBuilder.object().add(field, JsonBuilder.object()
                        .addProperty("value", query.getTerm().text()).addProperty("boost", query.getBoost())))
                .build();

        return wrapQueryForNestedIfRequired(field, wildcardQuery);
    }

    private static JsonObject convertFuzzyQuery(FuzzyQuery query) {
        String field = query.getTerm().field();

        JsonObject fuzzyQuery = JsonBuilder.object().add("fuzzy", JsonBuilder.object().add(field, JsonBuilder
                .object().addProperty("value", query.getTerm().text()).addProperty("fuzziness", query.getMaxEdits())
                .addProperty("prefix_length", query.getPrefixLength()).addProperty("boost", query.getBoost())))
                .build();

        return wrapQueryForNestedIfRequired(field, fuzzyQuery);
    }

    private static JsonObject convertPhraseQuery(PhraseQuery query) {
        Term[] terms = query.getTerms();

        if (terms.length == 0) {
            throw LOG.cannotQueryOnEmptyPhraseQuery();
        }

        String field = terms[0].field(); // phrase queries are only supporting one field
        StringBuilder phrase = new StringBuilder();
        for (Term term : terms) {
            phrase.append(" ").append(term.text());
        }

        JsonObject phraseQuery = JsonBuilder.object()
                .add("match_phrase", JsonBuilder.object().add(field,
                        JsonBuilder.object().addProperty("query", phrase.toString().trim())
                                .addProperty("slop", query.getSlop()).addProperty("boost", query.getBoost())))
                .build();

        return wrapQueryForNestedIfRequired(field, phraseQuery);
    }

    private static JsonObject convertTermRangeQuery(TermRangeQuery query) {
        JsonObject interval = new JsonObject();

        if (query.getLowerTerm() != null) {
            interval.addProperty(query.includesLower() ? "gte" : "gt", query.getLowerTerm().utf8ToString());
        }
        if (query.getUpperTerm() != null) {
            interval.addProperty(query.includesUpper() ? "lte" : "lt", query.getUpperTerm().utf8ToString());
        }
        interval.addProperty("boost", query.getBoost());

        JsonObject range = JsonBuilder.object().add("range", JsonBuilder.object().add(query.getField(), interval))
                .build();

        return wrapQueryForNestedIfRequired(query.getField(), range);
    }

    private static JsonObject convertNumericRangeQuery(NumericRangeQuery<?> query) {
        JsonObject interval = new JsonObject();
        if (query.getMin() != null) {
            interval.addProperty(query.includesMin() ? "gte" : "gt", query.getMin());
        }
        if (query.getMax() != null) {
            interval.addProperty(query.includesMax() ? "lte" : "lt", query.getMax());
        }
        interval.addProperty("boost", query.getBoost());

        JsonObject range = JsonBuilder.object().add("range", JsonBuilder.object().add(query.getField(), interval))
                .build();

        return wrapQueryForNestedIfRequired(query.getField(), range);
    }

    private static JsonObject convertConstantScoreQuery(ConstantScoreQuery query) {
        JsonObject constantScoreQuery = JsonBuilder
                .object().add("constant_score", JsonBuilder.object()
                        .add("filter", fromLuceneQuery(query.getQuery())).addProperty("boost", query.getBoost()))
                .build();

        return constantScoreQuery;
    }

    private static JsonObject convertFilteredQuery(FilteredQuery query) {
        JsonObject filteredQuery = JsonBuilder.object()
                .add("filtered", JsonBuilder.object().add("query", fromLuceneQuery(query.getQuery()))
                        .add("filter", fromLuceneQuery(query.getFilter())).addProperty("boost", query.getBoost()))
                .build();

        return filteredQuery;
    }

    private static JsonObject convertDistanceFilter(DistanceFilter filter) {
        JsonObject distanceQuery = JsonBuilder.object()
                .add("geo_distance",
                        JsonBuilder.object().addProperty("distance", filter.getRadius() + "km").add(
                                filter.getCoordinatesField(),
                                JsonBuilder.object().addProperty("lat", filter.getCenter().getLatitude())
                                        .addProperty("lon", filter.getCenter().getLongitude())))
                .build();

        distanceQuery = wrapQueryForNestedIfRequired(filter.getCoordinatesField(), distanceQuery);

        // we only implement the previous filter optimization when we use the hash method as Elasticsearch
        // automatically optimize the geo_distance query with a bounding box filter so we don't need to do it
        // ourselves when we use the range method.
        Filter previousFilter = filter.getPreviousFilter();
        if (previousFilter instanceof SpatialHashFilter) {
            distanceQuery = JsonBuilder.object().add("filtered", JsonBuilder.object().add("query", distanceQuery)
                    .add("filter", convertSpatialHashFilter((SpatialHashFilter) previousFilter))).build();
        }

        return distanceQuery;
    }

    private static JsonObject convertSpatialHashFilter(SpatialHashFilter filter) {
        JsonArray cellsIdsJsonArray = new JsonArray();
        for (String cellId : filter.getSpatialHashCellsIds()) {
            cellsIdsJsonArray.add(cellId);
        }

        JsonObject spatialHashFilter = JsonBuilder.object()
                .add("terms", JsonBuilder.object().add(filter.getFieldName(), cellsIdsJsonArray)).build();

        return wrapQueryForNestedIfRequired(filter.getFieldName(), spatialHashFilter);
    }

    private static JsonObject wrapQueryForNestedIfRequired(String field, JsonObject query) {
        if (!isNested(field)) {
            return query;
        }
        String path = FieldHelper.getEmbeddedFieldPath(field);

        return JsonBuilder.object()
                .add("nested", JsonBuilder.object().addProperty("path", path).add("query", query)).build();
    }

    private static boolean isNested(String field) {
        //TODO Drive through meta-data
        //      return FieldHelper.isEmbeddedField( field );
        return false;
    }

    public static JsonObject fromLuceneFilter(Filter luceneFilter) {
        if (luceneFilter instanceof QueryWrapperFilter) {
            Query query = ((QueryWrapperFilter) luceneFilter).getQuery();
            query.setBoost(luceneFilter.getBoost() * query.getBoost());
            return fromLuceneQuery(query);
        } else if (luceneFilter instanceof DistanceFilter) {
            return convertDistanceFilter((DistanceFilter) luceneFilter);
        } else if (luceneFilter instanceof SpatialHashFilter) {
            return convertSpatialHashFilter((SpatialHashFilter) luceneFilter);
        }
        throw LOG.cannotTransformLuceneFilterIntoEsQuery(luceneFilter);
    }

}