All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertigo.dynamo.plugins.search.elasticsearch.ESFacetedQueryResultBuilder Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
/**
 * vertigo - simple java starter
 *
 * Copyright (C) 2013, KleeGroup, [email protected] (http://www.kleegroup.com)
 * KleeGroup, Centre d'affaire la Boursidiere - BP 159 - 92357 Le Plessis Robinson Cedex - France
 *
 * Licensed 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 io.vertigo.dynamo.plugins.search.elasticsearch;

import io.vertigo.dynamo.collections.ListFilter;
import io.vertigo.dynamo.collections.metamodel.FacetDefinition;
import io.vertigo.dynamo.collections.metamodel.FacetedQueryDefinition;
import io.vertigo.dynamo.collections.model.Facet;
import io.vertigo.dynamo.collections.model.FacetValue;
import io.vertigo.dynamo.collections.model.FacetedQueryResult;
import io.vertigo.dynamo.domain.metamodel.DtDefinition;
import io.vertigo.dynamo.domain.metamodel.DtField;
import io.vertigo.dynamo.domain.model.DtList;
import io.vertigo.dynamo.domain.model.DtObject;
import io.vertigo.dynamo.search.metamodel.SearchIndexDefinition;
import io.vertigo.dynamo.search.model.SearchIndex;
import io.vertigo.dynamo.search.model.SearchQuery;
import io.vertigo.lang.Assertion;
import io.vertigo.lang.Builder;
import io.vertigo.lang.MessageText;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
import org.elasticsearch.search.aggregations.metrics.tophits.TopHits;
import org.elasticsearch.search.highlight.HighlightField;

//vérifier
/**
 * Requête physique d'accès à ElasticSearch.
 * Le driver exécute les requêtes de façon synchrone dans le contexte transactionnelle de la ressource.
 * @author pchretien, npiedeloup
 * @param  Type de l'objet représentant l'index
 */
final class ESFacetedQueryResultBuilder implements Builder> {

	private static final String TOPHITS_SUBAGGREAGTION_NAME = "top";

	private final ESDocumentCodec esDocumentCodec;
	private final SearchIndexDefinition indexDefinition;
	private final SearchResponse queryResponse;
	private final SearchQuery searchQuery;

	/**
	 * Constructor.
	 * @param esDocumentCodec Translation codec from Index Dto to document
	 * @param indexDefinition Index definition
	 * @param queryResponse ES Query response
	 * @param searchQuery Search query
	 */
	public ESFacetedQueryResultBuilder(final ESDocumentCodec esDocumentCodec, final SearchIndexDefinition indexDefinition, final SearchResponse queryResponse, final SearchQuery searchQuery) {
		Assertion.checkNotNull(esDocumentCodec);
		Assertion.checkNotNull(indexDefinition);
		Assertion.checkNotNull(queryResponse);
		Assertion.checkNotNull(searchQuery);
		//-----
		this.esDocumentCodec = esDocumentCodec;
		this.indexDefinition = indexDefinition;
		this.queryResponse = queryResponse;
		this.searchQuery = searchQuery;
	}

	/** {@inheritDoc} */
	@Override
	public FacetedQueryResult build() {
		final Map> resultHighlights = new HashMap<>();
		final Map> resultCluster;
		final DtList dtc = new DtList<>(indexDefinition.getIndexDtDefinition());
		if (searchQuery.isClusteringFacet()) {
			final Map dtcIndex = new LinkedHashMap<>();
			resultCluster = createCluster(dtcIndex);
			dtc.addAll(dtcIndex.values());
		} else {
			for (final SearchHit searchHit : queryResponse.getHits()) {
				final SearchIndex index = esDocumentCodec.searchHit2Index(indexDefinition, searchHit);
				final I result = index.getIndexDtObject();
				dtc.add(result);
				final Map highlights = createHighlight(searchHit, indexDefinition.getIndexDtDefinition());
				resultHighlights.put(result, highlights);
			}
			resultCluster = Collections.emptyMap();
		}
		//On fabrique à la volée le résultat.
		final List facets = createFacetList(searchQuery, queryResponse);
		final long count = queryResponse.getHits().getTotalHits();
		return new FacetedQueryResult<>(searchQuery.getFacetedQuery(), count, dtc, facets, resultCluster, resultHighlights, searchQuery);

	}

	private Map> createCluster(final Map dtcIndex) {
		final Map> resultCluster = new LinkedHashMap<>();
		final FacetDefinition facetDefinition = searchQuery.getClusteringFacetDefinition();
		final Aggregation facetAggregation = queryResponse.getAggregations().get(facetDefinition.getName());
		if (facetDefinition.isRangeFacet()) {
			//Cas des facettes par 'range'
			final MultiBucketsAggregation multiBuckets = (MultiBucketsAggregation) facetAggregation;
			for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
				final Bucket value = multiBuckets.getBucketByKey(facetRange.getListFilter().getFilterValue());
				populateCluster(value, facetRange, resultCluster, dtcIndex);
			}
		} else {
			//Cas des facettes par 'term'
			final MultiBucketsAggregation multiBuckets = (MultiBucketsAggregation) facetAggregation;
			FacetValue facetValue;
			for (final Bucket value : multiBuckets.getBuckets()) {
				final String query = facetDefinition.getDtField().name() + ":\"" + value.getKey() + "\"";
				final MessageText label = new MessageText(value.getKey(), null);
				facetValue = new FacetValue(new ListFilter(query), label);
				populateCluster(value, facetValue, resultCluster, dtcIndex);
			}
		}
		return resultCluster;
	}

	private void populateCluster(final Bucket value, final FacetValue facetValue, final Map> resultCluster, final Map dtcIndex) {
		final SearchHits facetSearchHits = ((TopHits) value.getAggregations().get(TOPHITS_SUBAGGREAGTION_NAME)).getHits();
		final DtList facetDtc = new DtList<>(indexDefinition.getIndexDtDefinition());
		for (final SearchHit searchHit : facetSearchHits) {
			I result = dtcIndex.get(searchHit.getId());
			if (result == null) {
				final SearchIndex index = esDocumentCodec.searchHit2Index(indexDefinition, searchHit);
				result = index.getIndexDtObject();
				dtcIndex.put(searchHit.getId(), result);
			}
			facetDtc.add(result);
		}
		resultCluster.put(facetValue, facetDtc);
	}

	private static Map createHighlight(final SearchHit searchHit, final DtDefinition resultDtDefinition) {
		final Map highlights = new HashMap<>();
		final Map highlightsMap = searchHit.getHighlightFields();

		for (final Map.Entry entry : highlightsMap.entrySet()) {
			final String fieldName = entry.getKey();
			if (resultDtDefinition.contains(fieldName)) { //We only keep highlighs match on result's fields
				final DtField dtField = resultDtDefinition.getField(fieldName);
				final StringBuilder sb = new StringBuilder();
				for (final Text fragment : entry.getValue().getFragments()) {
					sb.append("").append(fragment).append("");
				}
				highlights.put(dtField, sb.toString());
			}
		}
		return highlights;
	}

	private static List createFacetList(final SearchQuery searchQuery, final SearchResponse queryResponse) {
		final List facets = new ArrayList<>();
		if (searchQuery.getFacetedQuery().isDefined() && queryResponse.getAggregations() != null) {
			final FacetedQueryDefinition queryDefinition = searchQuery.getFacetedQuery().get().getDefinition();
			for (final FacetDefinition facetDefinition : queryDefinition.getFacetDefinitions()) {
				final Aggregation aggregation = queryResponse.getAggregations().get(facetDefinition.getName());
				if (aggregation != null) {
					final Facet currentFacet;
					if (facetDefinition.isRangeFacet()) {
						//Cas des facettes par 'range'
						final MultiBucketsAggregation rangeBuckets = (MultiBucketsAggregation) aggregation;
						currentFacet = createFacetRange(facetDefinition, rangeBuckets);
					} else {
						//Cas des facettes par 'term'
						final MultiBucketsAggregation multiBuckets = (MultiBucketsAggregation) aggregation;
						currentFacet = createTermFacet(facetDefinition, multiBuckets);
					}
					facets.add(currentFacet);
				}
			}
		}
		return facets;
	}

	private static Facet createTermFacet(final FacetDefinition facetDefinition, final MultiBucketsAggregation multiBuckets) {
		final Map facetValues = new LinkedHashMap<>();
		FacetValue facetValue;
		for (final Bucket value : multiBuckets.getBuckets()) {
			final MessageText label = new MessageText(value.getKey(), null);
			final String query = facetDefinition.getDtField().name() + ":\"" + value.getKey() + "\"";
			facetValue = new FacetValue(new ListFilter(query), label);
			facetValues.put(facetValue, value.getDocCount());
		}

		return new Facet(facetDefinition, facetValues);
	}

	private static Facet createFacetRange(final FacetDefinition facetDefinition, final MultiBucketsAggregation rangeBuckets) {
		//Cas des facettes par range
		final Map rangeValues = new LinkedHashMap<>();
		for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
			final Bucket value = rangeBuckets.getBucketByKey(facetRange.getListFilter().getFilterValue());
			rangeValues.put(facetRange, value.getDocCount());
		}
		return new Facet(facetDefinition, rangeValues);
	}

}