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

io.vertigo.datafactory.plugins.search.elasticsearch.AsbtractESSearchRequestBuilder Maven / Gradle / Ivy

The newest version!
/*
 * vertigo - application development platform
 *
 * Copyright (C) 2013-2024, Vertigo.io, [email protected]
 *
 * 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.datafactory.plugins.search.elasticsearch;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator.KeyedFilter;
import org.elasticsearch.search.aggregations.bucket.range.DateRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.GeoDistanceAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.TopHitsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;

import io.vertigo.core.lang.Assertion;
import io.vertigo.core.lang.BasicType;
import io.vertigo.core.lang.BasicTypeAdapter;
import io.vertigo.core.lang.Builder;
import io.vertigo.core.lang.VSystemException;
import io.vertigo.core.util.BeanUtil;
import io.vertigo.datafactory.collections.ListFilter;
import io.vertigo.datafactory.collections.definitions.FacetDefinition;
import io.vertigo.datafactory.collections.definitions.FacetedQueryDefinition;
import io.vertigo.datafactory.collections.model.FacetValue;
import io.vertigo.datafactory.collections.model.FacetedQuery;
import io.vertigo.datafactory.impl.collections.functions.filter.DtListPatternFilterUtil;
import io.vertigo.datafactory.impl.search.dsl.model.DslGeoDistanceQuery;
import io.vertigo.datafactory.impl.search.dsl.model.DslGeoExpression;
import io.vertigo.datafactory.impl.search.dsl.model.DslGeoRangeQuery;
import io.vertigo.datafactory.impl.search.dsl.rules.DslParserUtil;
import io.vertigo.datafactory.search.model.SearchQuery;
import io.vertigo.datamodel.data.definitions.DataDefinition;
import io.vertigo.datamodel.data.definitions.DataField;
import io.vertigo.datamodel.data.model.DtListState;
import io.vertigo.datamodel.smarttype.definitions.DtProperty;

/**
 * ElasticSearch request builder from searchManager api.
 * @author pchretien, npiedeloup
 * @param R Type of ES searchRequest : SearchRequest for HLClent or SearchRequestBuilder for TransportClient
 * @param S Type of ES SearchSourceBuilder : SearchSourceBuilder for HLClent or SearchRequestBuilder for TransportClient
 */
public abstract class AsbtractESSearchRequestBuilder implements Builder {

	protected static final int MAX_TOTAL_HIT = 1_000_000; //maximum total hit count (then just know > 1 000 000)

	private static final int TERM_AGGREGATION_SIZE = 50; //max 50 facets values per facet
	private static final int TOPHITS_SUBAGGREGATION_MAXSIZE = 100; //max 100 documents per cluster when clusterization is used
	private static final int TOPHITS_SUBAGGREGATION_SIZE = 10; //max 10 documents per cluster when clusterization is used
	private static final String TOPHITS_SUBAGGREGATION_NAME = "top";
	private static final String DATE_PATTERN = "dd/MM/yyyy";
	private static final Pattern RANGE_PATTERN = Pattern.compile("([a-z][a-zA-Z0-9]*):([\\[\\{])(.*) TO (.*)([\\}\\]])");
	private static final Pattern SIMPLE_CRITERIA_PATTERN = Pattern.compile("#([a-z][a-zA-Z0-9]*)#");

	private final Map myTypeAdapters;
	private DataDefinition myIndexDtDefinition;
	private SearchQuery mySearchQuery;
	private DtListState myListState;
	private int myDefaultMaxRows = 10;
	private boolean myUseHighlight = false;

	/**
	 * @param typeAdapters Mapping to basic type adapter
	 */
	public AsbtractESSearchRequestBuilder(final Map typeAdapters) {
		Assertion.check().isNotNull(typeAdapters);
		//-----
		this.myTypeAdapters = typeAdapters;
	}

	/**
	 * @param indexDtDefinition Index dtDefinition
	 * @return this builder
	 */
	public T withIndexDtDefinition(final DataDefinition indexDtDefinition) {
		Assertion.check().isNotNull(indexDtDefinition);
		//-----
		myIndexDtDefinition = indexDtDefinition;
		return (T) this;
	}

	/**
	 * @param searchQuery Search query
	 * @return this builder
	 */
	public T withSearchQuery(final SearchQuery searchQuery) {
		Assertion.check().isNotNull(searchQuery);
		//-----
		mySearchQuery = searchQuery;
		return (T) this;
	}

	/**
	 * @param listState List state
	 * @param defaultMaxRows default max rows
	 * @return this builder
	 */
	public T withListState(final DtListState listState, final int defaultMaxRows) {
		Assertion.check().isNotNull(listState);
		//-----
		myListState = listState;
		myDefaultMaxRows = defaultMaxRows;
		return (T) this;
	}

	/**
	 * @return this builder
	 */
	public T withHighlight() {
		myUseHighlight = true;
		return (T) this;
	}

	/** {@inheritDoc} */
	@Override
	public R build() {
		Assertion.check()
				.isNotNull(myIndexDtDefinition, "You must set Index DtDefinition")
				.isNotNull(mySearchQuery, "You must set SearchQuery")
				.isNotNull(myListState, "You must set ListState")
				.when(mySearchQuery.isClusteringFacet() && myListState.getMaxRows().isPresent(), () -> Assertion.check() //si il y a un cluster on vérifie le maxRows
						.isTrue(myListState.getMaxRows().get() < TOPHITS_SUBAGGREGATION_MAXSIZE,
								"ListState.top = {0} invalid. Can't show more than {1} elements when grouping", myListState.getMaxRows().orElse(null), TOPHITS_SUBAGGREGATION_MAXSIZE));
		//-----
		appendListState(mySearchQuery, myListState, myDefaultMaxRows, myIndexDtDefinition, myTypeAdapters);
		appendSearchQuery(mySearchQuery, getSearchSourceBuilder(), myUseHighlight, myTypeAdapters);
		appendFacetDefinition(mySearchQuery, getSearchSourceBuilder(), myIndexDtDefinition, myListState, myUseHighlight, myTypeAdapters);
		return getSearchRequest();
	}

	protected abstract S getSearchSourceBuilder();

	protected abstract R getSearchRequest();

	protected abstract void appendListState(SearchQuery searchQuery, DtListState listState, int defaultMaxRows, DataDefinition indexDtDefinition, Map typeAdapters);

	protected abstract void setQueryAndPostFilter(QueryBuilder requestQueryBuilder, BoolQueryBuilder postFilterBoolQueryBuilder);

	protected abstract void setHighlighter(HighlightBuilder highlightBuilder);

	protected abstract void addAggregation(S searchRequestBuilder, AggregationBuilder aggregationBuilder);

	protected FieldSortBuilder getFieldSortBuilder(final DataDefinition indexDefinition, final DtListState listState) {
		final DataField sortField = indexDefinition.getField(listState.getSortFieldName().get());
		String sortIndexFieldName = sortField.name();
		final IndexType indexType = IndexType.readIndexType(sortField.smartTypeDefinition());

		if (indexType.isIndexSubKeyword()) { //s'il y a un subKeyword on tri dessus
			sortIndexFieldName = sortIndexFieldName + ".keyword";
		}
		return SortBuilders.fieldSort(sortIndexFieldName)
				.order(listState.isSortDesc().get() ? SortOrder.DESC : SortOrder.ASC);
	}

	private void appendSearchQuery(final SearchQuery searchQuery, final S searchRequestBuilder, final boolean useHighlight, final Map typeAdapters) {
		final BoolQueryBuilder filterBoolQueryBuilder = QueryBuilders.boolQuery();
		final BoolQueryBuilder postFilterBoolQueryBuilder = QueryBuilders.boolQuery();

		//on ajoute les critères de la recherche AVEC impact sur le score
		appendSearchQuery(searchQuery, filterBoolQueryBuilder, typeAdapters);

		//on ajoute les filtres de sécurité SANS impact sur le score
		appendSecurityFilter(searchQuery.getSecurityListFilter(), filterBoolQueryBuilder);

		//on ajoute les filtres des facettes SANS impact sur le score
		appendSelectedFacetValues(searchQuery.getFacetedQuery(), filterBoolQueryBuilder, postFilterBoolQueryBuilder, searchQuery.getCriteria(), typeAdapters);

		final QueryBuilder requestQueryBuilder;
		if (searchQuery.isBoostMostRecent()) {
			requestQueryBuilder = appendBoostMostRecent(searchQuery, filterBoolQueryBuilder);
		} else {
			requestQueryBuilder = filterBoolQueryBuilder;
		}
		setQueryAndPostFilter(requestQueryBuilder, postFilterBoolQueryBuilder);

		if (useHighlight) {
			//.setHighlighterFilter(true) //We don't highlight the security filter
			setHighlighter(new HighlightBuilder().numOfFragments(3));
		}
	}

	private static QueryBuilder appendSearchQuery(final SearchQuery searchQuery, final BoolQueryBuilder filterBoolQueryBuilder, final Map typeAdapters) {
		final QueryBuilder queryBuilder = translateToQueryBuilder(searchQuery.getListFilter());
		filterBoolQueryBuilder.must(queryBuilder);
		final Optional geoExpression = searchQuery.getGeoExpression();
		if (geoExpression.isPresent()) {
			filterBoolQueryBuilder.must(DslGeoToQueryBuilderUtil.translateToQueryBuilder(geoExpression.get(), searchQuery.getCriteria(), typeAdapters));
		}
		return queryBuilder;
	}

	private static void appendSecurityFilter(final Optional securityListFilter, final BoolQueryBuilder filterBoolQueryBuilder) {
		if (securityListFilter.isPresent()) {
			final QueryBuilder securityFilterBuilder = translateToQueryBuilder(securityListFilter.get());
			filterBoolQueryBuilder.filter(securityFilterBuilder);
			//use filteredQuery instead of PostFilter in order to filter aggregations too.
		}
	}

	private static void appendSelectedFacetValues(final Optional facetedQuery, final BoolQueryBuilder filterBoolQueryBuilder, final BoolQueryBuilder postFilterBoolQueryBuilder, final Object myCriteria, final Map typeAdapters) {
		if (facetedQuery.isPresent()) {
			for (final FacetDefinition facetDefinition : facetedQuery.get().getDefinition().getFacetDefinitions()) {
				final boolean useSubKeywordField = useSubKeywordFieldForFacet(facetDefinition);
				if (facetDefinition.isMultiSelectable()) {
					appendSelectedFacetValuesFilter(postFilterBoolQueryBuilder, facetedQuery.get().getSelectedFacetValues().getFacetValues(facetDefinition.getName()), facetDefinition.getDataField(), useSubKeywordField);
				} else if (isGeoField(facetDefinition.getDataField())) {
					appendSelectedGeoFacetValuesFilter(filterBoolQueryBuilder, facetedQuery.get().getSelectedFacetValues().getFacetValues(facetDefinition.getName()), myCriteria, typeAdapters);
				} else {
					appendSelectedFacetValuesFilter(filterBoolQueryBuilder, facetedQuery.get().getSelectedFacetValues().getFacetValues(facetDefinition.getName()), facetDefinition.getDataField(), useSubKeywordField);
				}
			}
		}
	}

	private static boolean useSubKeywordFieldForFacet(final FacetDefinition facetDefinition) {
		final IndexType indexType = IndexType.readIndexType(facetDefinition.getDataField().smartTypeDefinition());
		//si il y a un sub keyword on le prend (sinon le facetable permet d'avoir un DataField, mais il peut etre tokenized)
		return indexType.isIndexSubKeyword();
	}

	private static boolean isGeoField(final DataField dtField) {
		final String indexType = dtField.smartTypeDefinition().getProperties().getValue(DtProperty.INDEX_TYPE);
		return indexType != null && indexType.indexOf("geo_point") != -1;
	}

	private static void appendSelectedFacetValuesFilter(final BoolQueryBuilder filterBoolQueryBuilder, final List facetValues, final DataField facetField, final boolean useSubKeywordField) {
		if (facetValues.size() == 1) {
			filterBoolQueryBuilder.filter(translateToQueryBuilder(facetValues.get(0).listFilter(),
					useSubKeywordField ? Collections.singleton(facetField) : Collections.emptySet()));
		} else if (facetValues.size() > 1) {
			final BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
			for (final FacetValue facetValue : facetValues) {
				boolQueryBuilder.should(translateToQueryBuilder(facetValue.listFilter(),
						useSubKeywordField ? Collections.singleton(facetField) : Collections.emptySet()));//on ajoute les valeurs en OU
			}
			filterBoolQueryBuilder.filter(boolQueryBuilder);
		}
	}

	private static void appendSelectedGeoFacetValuesFilter(final BoolQueryBuilder filterBoolQueryBuilder, final List facetValues, final Object myCriteria, final Map typeAdapters) {
		if (facetValues.size() == 1) {
			final DslGeoExpression geoExpression = DslParserUtil.parseGeoExpression(facetValues.get(0).listFilter().getFilterValue());
			filterBoolQueryBuilder.filter(DslGeoToQueryBuilderUtil.translateToQueryBuilder(geoExpression, myCriteria, typeAdapters));
		} else if (facetValues.size() > 1) {
			final BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
			for (final FacetValue facetValue : facetValues) {
				final DslGeoExpression geoExpression = DslParserUtil.parseGeoExpression(facetValue.listFilter().getFilterValue());
				boolQueryBuilder.should(DslGeoToQueryBuilderUtil.translateToQueryBuilder(geoExpression, myCriteria, typeAdapters));//on ajoute les valeurs en OU
			}
			filterBoolQueryBuilder.filter(boolQueryBuilder);
		}
	}

	private static QueryBuilder appendBoostMostRecent(final SearchQuery searchQuery, final QueryBuilder queryBuilder) {
		return QueryBuilders.functionScoreQuery(
				queryBuilder,
				new ExponentialDecayFunctionBuilder(searchQuery.getBoostedDocumentDateField(), null, searchQuery.getNumDaysOfBoostRefDocument() + "d", "1d", searchQuery.getMostRecentBoost() - 1D));
	}

	private void appendFacetDefinition(
			final SearchQuery searchQuery,
			final S searchRequestBuilder,
			final DataDefinition indexDefinition,
			final DtListState listState,
			final boolean useHighlight,
			final Map typeAdapters) {
		Assertion.check().isNotNull(searchRequestBuilder);
		//-----
		//On ajoute le cluster, si présent
		if (searchQuery.isClusteringFacet()) { //si il y a un cluster on le place en premier
			final FacetDefinition clusteringFacetDefinition = searchQuery.getClusteringFacetDefinition();

			final AggregationBuilder aggregationBuilder = facetToAggregationBuilder(clusteringFacetDefinition, searchQuery.getCriteria(), typeAdapters);
			final TopHitsAggregationBuilder topHitsBuilder = AggregationBuilders.topHits(TOPHITS_SUBAGGREGATION_NAME)
					.size(listState.getMaxRows().orElse(TOPHITS_SUBAGGREGATION_SIZE))
					.from(listState.getSkipRows());

			if (useHighlight) {
				topHitsBuilder.highlighter(new HighlightBuilder().numOfFragments(3));//.addHighlightedField("*"); HOW TO ?
			}

			if (listState.getSortFieldName().isPresent()) {
				topHitsBuilder.sort(getFieldSortBuilder(indexDefinition, listState));
			}

			aggregationBuilder.subAggregation(topHitsBuilder);
			//We fetch source, because it's our only source to create result list
			addAggregation(searchRequestBuilder, aggregationBuilder);
		}
		//Puis les facettes liées à la query, si présent
		if (searchQuery.getFacetedQuery().isPresent()) {
			final FacetedQuery facetedQuery = searchQuery.getFacetedQuery().get();
			final FacetedQueryDefinition facetedQueryDefinition = facetedQuery.getDefinition();
			final Collection facetDefinitions = new ArrayList<>(facetedQueryDefinition.getFacetDefinitions());
			if (searchQuery.isClusteringFacet() && facetDefinitions.contains(searchQuery.getClusteringFacetDefinition())) {
				facetDefinitions.remove(searchQuery.getClusteringFacetDefinition());
			}
			for (final FacetDefinition facetDefinition : facetDefinitions) {
				final AggregationBuilder aggregationBuilder = facetToAggregationBuilder(facetDefinition, searchQuery.getCriteria(), typeAdapters);
				final BoolQueryBuilder aggsFilterBoolQueryBuilder = QueryBuilders.boolQuery();
				for (final FacetDefinition filterFacetDefinition : facetedQuery.getDefinition().getFacetDefinitions()) {
					if (filterFacetDefinition.isMultiSelectable() && !facetDefinition.equals(filterFacetDefinition)) {
						final boolean useSubKeywordField = useSubKeywordFieldForFacet(facetDefinition);
						//on ne doit refiltrer que les multiSelectable (les autres sont dans le filter de la request), sauf la facet qu'on est entrain de traiter
						appendSelectedFacetValuesFilter(aggsFilterBoolQueryBuilder, facetedQuery.getSelectedFacetValues().getFacetValues(filterFacetDefinition.getName()), facetDefinition.getDataField(), useSubKeywordField);
					}
				}
				if (aggsFilterBoolQueryBuilder.hasClauses()) {
					final AggregationBuilder filterAggregationBuilder = AggregationBuilders.filter(facetDefinition.getName() + "Filter", aggsFilterBoolQueryBuilder);
					filterAggregationBuilder.subAggregation(aggregationBuilder);
					addAggregation(searchRequestBuilder, filterAggregationBuilder);
				} else {
					addAggregation(searchRequestBuilder, aggregationBuilder);
				}
			}
		}
	}

	private static AggregationBuilder facetToAggregationBuilder(final FacetDefinition facetDefinition, final Object myCriteria, final Map typeAdapters) {
		final DataField dtField = facetDefinition.getDataField();
		if (facetDefinition.isCustomFacet()) {
			return customFacetToAggregationBuilder(facetDefinition, dtField, myCriteria, typeAdapters);
		} else if (facetDefinition.isRangeFacet()) {
			return rangeFacetToAggregationBuilder(facetDefinition, dtField, myCriteria, typeAdapters);
		}
		return termFacetToAggregationBuilder(facetDefinition, dtField);
	}

	private static AggregationBuilder termFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField) {
		//facette par field
		final BucketOrder facetOrder = switch (facetDefinition.getOrder()) {
			case alpha -> BucketOrder.key(true);
			case count -> BucketOrder.count(false);
			case definition -> null; //ES accept null for no sorting
		};
		//Warning term aggregations are inaccurate : see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html
		String fieldName = dtField.name();
		if (useSubKeywordFieldForFacet(facetDefinition)) {
			fieldName = fieldName + ".keyword";
		}
		return AggregationBuilders.terms(facetDefinition.getName())
				.size(TERM_AGGREGATION_SIZE)
				.field(fieldName)
				.order(facetOrder);
	}

	private static AggregationBuilder customFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField, final Object myCriteria, final Map typeAdapters) {
		final Map customParams = replaceCriteria(facetDefinition.getCustomParams(), myCriteria);
		return new CustomAggregationBuilder(facetDefinition.getName(), facetDefinition.getDataField().name(), customParams);
	}

	private static Map replaceCriteria(final Map customParams, final Object myCriteria) {
		return customParams.entrySet().stream()
				.collect(Collectors.toMap(Entry::getKey,
						v -> {
							final Matcher matcher = SIMPLE_CRITERIA_PATTERN.matcher(v.getValue());
							String result = v.getValue();
							while (matcher.find()) {
								final String fieldName = matcher.group(1);
								final Object fieldValue = BeanUtil.getValue(myCriteria, fieldName);
								if (fieldValue != null) {
									result = result.replaceAll("#" + fieldName + "#", String.valueOf(fieldValue));
								}
							}
							return result;
						}));
	}

	private static AggregationBuilder rangeFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField, final Object myCriteria, final Map typeAdapters) {
		//facette par range
		switch (dtField.smartTypeDefinition().getScope()) {
			case BASIC_TYPE:
				final BasicType dataType = dtField.smartTypeDefinition().getBasicType();
				if (dataType == BasicType.LocalDate) {
					return dateRangeFacetToAggregationBuilder(facetDefinition, dtField);
				} else if (dataType.isNumber()) {
					return numberRangeFacetToAggregationBuilder(facetDefinition, dtField);
				}
				break;
			case VALUE_TYPE:
				return geoRangeFacetToAggregationBuilder(facetDefinition, dtField, myCriteria, typeAdapters);
			case DATA_TYPE:
			default:
				throw new IllegalArgumentException("Type de donnée non pris en charge comme Facet pour le keyconcept indexé [" + dtField.smartTypeDefinition() + "].");
		}

		final List filters = new ArrayList<>();
		for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
			final String filterValue = facetRange.listFilter().getFilterValue();
			Assertion.check().isTrue(filterValue.contains(dtField.name()), "RangeFilter query ({1}) should use defined fieldName {0}", dtField.name(), filterValue);
			filters.add(new KeyedFilter(facetRange.code(), QueryBuilders.queryStringQuery(filterValue)));
		}
		return AggregationBuilders.filters(facetDefinition.getName(), filters.toArray(new KeyedFilter[filters.size()]));
	}

	private static AggregationBuilder numberRangeFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField) {
		final RangeAggregationBuilder rangeBuilder = AggregationBuilders.range(facetDefinition.getName())//
				.field(dtField.name());
		for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
			final String filterValue = facetRange.listFilter().getFilterValue();
			Assertion.check().isTrue(filterValue.contains(dtField.name()), "RangeFilter query ({1}) should use defined fieldName {0}", dtField.name(), filterValue);
			final String[] parsedFilter = DtListPatternFilterUtil.parseFilter(filterValue, RANGE_PATTERN).get();
			final Optional minValue = convertToDouble(parsedFilter[3]);
			final Optional maxValue = convertToDouble(parsedFilter[4]);
			if (minValue.isEmpty()) {
				rangeBuilder.addUnboundedTo(facetRange.code(), maxValue.get());
			} else if (maxValue.isEmpty()) {
				rangeBuilder.addUnboundedFrom(facetRange.code(), minValue.get());
			} else {
				rangeBuilder.addRange(facetRange.code(), minValue.get(), maxValue.get()); //always min include and max exclude in ElasticSearch
			}
		}
		return rangeBuilder;
	}

	private static AggregationBuilder dateRangeFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField) {
		final DateRangeAggregationBuilder dateRangeBuilder = AggregationBuilders.dateRange(facetDefinition.getName())
				.field(dtField.name())
				.format(DATE_PATTERN);
		for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
			final String filterValue = facetRange.listFilter().getFilterValue();
			Assertion.check().isTrue(filterValue.contains(dtField.name()), "RangeFilter query ({1}) should use defined fieldName {0}", dtField.name(), filterValue);
			final String[] parsedFilter = DtListPatternFilterUtil.parseFilter(filterValue, RANGE_PATTERN).orElseThrow(() -> new VSystemException("Range Facet syntaxe invalid : " + filterValue));
			final String minValue = parsedFilter[3];
			final String maxValue = parsedFilter[4];
			if ("*".equals(minValue)) {
				dateRangeBuilder.addUnboundedTo(facetRange.code(), maxValue);
			} else if ("*".equals(maxValue)) {
				dateRangeBuilder.addUnboundedFrom(facetRange.code(), minValue);
			} else {
				dateRangeBuilder.addRange(facetRange.code(), minValue, maxValue); //always min include and max exclude in ElasticSearch
			}
		}
		return dateRangeBuilder;
	}

	private static AggregationBuilder geoRangeFacetToAggregationBuilder(final FacetDefinition facetDefinition, final DataField dtField, final Object myCriteria, final Map typeAdapters) {
		Assertion.check().isFalse(facetDefinition.getFacetRanges().isEmpty(), "Range facet can't be empty {0}", facetDefinition.getName());
		//-----
		String originExpression = null;
		GeoDistanceAggregationBuilder rangeBuilder = null;//AggregationBuilders.geoDistance(name, origin)range(facetDefinition.getName())//
		//.field(dtField.getName());
		for (final FacetValue facetRange : facetDefinition.getFacetRanges()) {
			final String filterValue = facetRange.listFilter().getFilterValue();
			final DslGeoExpression dslGeoExpression = DslParserUtil.parseGeoExpression(filterValue);
			final String geoFieldName = dslGeoExpression.getField().getFieldName();
			Assertion.check().isTrue(geoFieldName.contains(dtField.name()), "RangeFilter query ({1}) should use defined fieldName {0}", dtField.name(), filterValue);

			final DslGeoDistanceQuery geoStartDistanceQuery;
			final DslGeoDistanceQuery geoEndDistanceQuery;
			if (dslGeoExpression.getGeoQuery() instanceof DslGeoDistanceQuery) {
				geoEndDistanceQuery = (DslGeoDistanceQuery) dslGeoExpression.getGeoQuery();
				geoStartDistanceQuery = new DslGeoDistanceQuery(geoEndDistanceQuery.getGeoPoint(), 0, "m");
				if (rangeBuilder == null) {
					final GeoPoint geoPoint = DslGeoToQueryBuilderUtil.computeGeoPoint(geoEndDistanceQuery.getGeoPoint(), myCriteria, typeAdapters);
					originExpression = geoEndDistanceQuery.getGeoPoint().toString();
					rangeBuilder = AggregationBuilders.geoDistance(facetDefinition.getName(), geoPoint).field(geoFieldName);
				} else {
					Assertion.check().isTrue(geoEndDistanceQuery.getGeoPoint().toString().equals(originExpression), "All facets must have the same origin : {0} != {1} in {2}", geoEndDistanceQuery.getGeoPoint().toString(), originExpression, facetDefinition.getName());
				}
			} else if (dslGeoExpression.getGeoQuery() instanceof DslGeoRangeQuery) {
				final DslGeoRangeQuery geoRangeQuery = (DslGeoRangeQuery) dslGeoExpression.getGeoQuery();
				geoStartDistanceQuery = (DslGeoDistanceQuery) geoRangeQuery.getStartGeoPoint();
				geoEndDistanceQuery = (DslGeoDistanceQuery) geoRangeQuery.getEndGeoPoint();
				if (rangeBuilder == null) {
					final GeoPoint geoPoint = DslGeoToQueryBuilderUtil.computeGeoPoint(geoStartDistanceQuery.getGeoPoint(), myCriteria, typeAdapters);
					originExpression = geoStartDistanceQuery.getGeoPoint().toString();
					rangeBuilder = AggregationBuilders.geoDistance(facetDefinition.getName(), geoPoint).field(geoFieldName);
				} else {
					Assertion.check()
							.isTrue(geoStartDistanceQuery.getGeoPoint().toString().equals(originExpression), "All facets must have the same origin : {0} != {1} in {2}", geoStartDistanceQuery.getGeoPoint().toString(), originExpression, facetDefinition.getName())
							.isTrue(geoEndDistanceQuery.getGeoPoint().toString().equals(originExpression), "All facets must have the same origin : {0} != {1} in {2}", geoEndDistanceQuery.getGeoPoint().toString(), originExpression, facetDefinition.getName());
				}
			} else {
				throw new IllegalArgumentException("Only GeoDistanceQuery or Range of GeoDistanceQuery are supported in range facet (in " + facetDefinition.getName() + ")");
			}
			final DistanceUnit startDistanceUnit = DistanceUnit.fromString(geoStartDistanceQuery.getDistanceUnit());
			final DistanceUnit endDistanceUnit = DistanceUnit.fromString(geoEndDistanceQuery.getDistanceUnit());
			rangeBuilder.addRange(facetRange.code(), startDistanceUnit.toMeters(geoStartDistanceQuery.getDistance()), endDistanceUnit.toMeters(geoEndDistanceQuery.getDistance()));
		}
		return rangeBuilder;
	}

	private static Optional convertToDouble(final String valueToConvert) {
		final String stringValue = valueToConvert.trim();
		if ("*".equals(stringValue) || "".equals(stringValue)) {
			return Optional.empty();//pas de test
		}
		//--
		final Double result = Double.valueOf(stringValue);
		return Optional.of(result);
	}

	/**
	 * @param listFilter ListFilter
	 * @return QueryBuilder
	 */
	public static QueryBuilder translateToQueryBuilder(final ListFilter listFilter) {
		return translateToQueryBuilder(listFilter, Collections.emptySet());
	}

	/**
	 * @param listFilter ListFilter
	 * @return QueryBuilder
	 */
	public static QueryBuilder translateToQueryBuilder(final ListFilter listFilter, final Set keywordFields) {
		Assertion.check().isNotNull(listFilter);
		//-----
		String listFilterValue = listFilter.getFilterValue();
		for (final DataField keywordField : keywordFields) {
			listFilterValue = listFilterValue.replace(keywordField.name() + ":", keywordField.name() + ".keyword:");
		}

		final String query = " +(" +
				listFilterValue +
				')';
		return QueryBuilders.queryStringQuery(query)
				//.lowercaseExpandedTerms(false) ?? TODO maj version
				.analyzeWildcard(true);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy