package com.codetroopers.play.elasticsearch; import com.codetroopers.play.elasticsearch.jest.JestRichResult; import com.codetroopers.play.elasticsearch.jest.JestSearchRequestBuilder; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import io.searchbox.core.search.facet.Facet; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.elasticsearch.index.query.FilterBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.facet.FacetBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import play.Logger; import play.libs.F; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; /** * An ElasticSearch query * * @param <T> extends Index */ public class IndexQuery<T extends Index> { /** * Objet retourné dans les résultats */ private final Class<T> clazz; /** * Query searchRequestBuilder */ private QueryBuilder builder = QueryBuilders.matchAllQuery(); private String query = null; private List<FacetBuilder> facets = new ArrayList<>(); private List<SortBuilder> sorts = new ArrayList<>(); private int from = -1; private int size = -1; private boolean explain = false; private boolean noField = false; public IndexQuery(Class<T> clazz) { Validate.notNull(clazz, "clazz cannot be null"); this.clazz = clazz; } public IndexQuery<T> setBuilder(QueryBuilder builder) { this.builder = builder; return this; } public void setQuery(String query) { this.query = query; } public void setNoField(boolean noField) { this.noField = noField; } /** * Sets from * * @param from record index to start from * @return self */ public IndexQuery<T> from(int from) { this.from = from; return this; } /** * Sets fetch size * * @param size the fetch size * @return self */ public IndexQuery<T> size(int size) { this.size = size; return this; } public IndexQuery<T> setExplain(boolean explain) { this.explain = explain; return this; } /** * Adds a facet * * @param facet the facet * @return self */ public IndexQuery<T> addFacet(FacetBuilder facet) { Validate.notNull(facet, "facet cannot be null"); facets.add(facet); return this; } /** * Sorts the result by a specific field * * @param field the sort field * @param order the sort order * @return self */ public IndexQuery<T> addSort(String field, SortOrder order) { Validate.notEmpty(field, "field cannot be null"); Validate.notNull(order, "order cannot be null"); sorts.add(SortBuilders.fieldSort(field).order(order)); return this; } /** * Adds a generic {@link SortBuilder} * * @param sort the sort searchRequestBuilder * @return self */ public IndexQuery<T> addSort(SortBuilder sort) { Validate.notNull(sort, "sort cannot be null"); sorts.add(sort); return this; } /** * Runs the query * * @param indexQueryPath * @return */ public IndexResults<T> fetch(IndexQueryPath indexQueryPath) { return fetch(indexQueryPath, null); } /** * Runs the query with a filter * * @param indexQueryPath * @param filter * @return */ public IndexResults<T> fetch(IndexQueryPath indexQueryPath, FilterBuilder filter) { JestSearchRequestBuilder request = getSearchRequestBuilder(indexQueryPath, filter); return executeSearchRequest(request); } /** * Runs the query asynchronously * * @param indexQueryPath * @return */ public F.Promise<IndexResults<T>> fetchAsync(IndexQueryPath indexQueryPath) { return fetchAsync(indexQueryPath, null); } /** * Runs the query asynchronously with a filter * * @param indexQueryPath * @param filter * @return */ public F.Promise<IndexResults<T>> fetchAsync(final IndexQueryPath indexQueryPath, final FilterBuilder filter) { final F.Promise<JestRichResult> jestResultPromise = getSearchRequestBuilder(indexQueryPath, filter).executeAsync(); return jestResultPromise.map(new F.Function<JestRichResult, IndexResults<T>>() { @Override public IndexResults<T> apply(JestRichResult jestResult) throws Throwable { return toSearchResults(jestResult); } }); } public IndexResults<T> executeSearchRequest(JestSearchRequestBuilder request) { JestRichResult searchResponse = request.execute(); if (IndexClient.config.showRequest && searchResponse != null) { Logger.debug("ElasticSearch : Response -> " + searchResponse.getJsonString()); } return toSearchResults(searchResponse); } public JestSearchRequestBuilder getSearchRequestBuilder(IndexQueryPath indexQueryPath) { return getSearchRequestBuilder(indexQueryPath, null); } public JestSearchRequestBuilder getSearchRequestBuilder(FilterBuilder filter) { return getSearchRequestBuilder(null, filter); } public JestSearchRequestBuilder getSearchRequestBuilder(@Nullable IndexQueryPath indexQueryPath, FilterBuilder filter) { // Build request JestSearchRequestBuilder request = new JestSearchRequestBuilder(); if (indexQueryPath != null) { request.setIndices(indexQueryPath.index) .setTypes(indexQueryPath.type); } request.setSearchType(io.searchbox.params.SearchType.QUERY_THEN_FETCH) .setFilter(filter); // set Query if (StringUtils.isNotBlank(query)) { request.setQuery(query); } else { request.setQuery(builder); } // set no Fields -> only return id and type if (noField) { request.setNoFields(); } // Facets for (FacetBuilder facet : facets) { request.addFacet(facet); } // Sorting for (SortBuilder sort : sorts) { request.addSort(sort); } // Paging if (from > -1) { request.setFrom(from); } if (size > -1) { request.setSize(size); } // Explain if (explain) { request.setExplain(true); } if (IndexClient.config.showRequest) { if (StringUtils.isNotBlank(query)) { Logger.debug("ElasticSearch : Query -> " + query); } else { Logger.debug("ElasticSearch : Query -> " + builder.toString()); } } return request; } private IndexResults<T> toSearchResults(@NotNull JestRichResult jestRichResult) { List<T> results = Lists.newArrayList(); long count = jestRichResult.getTotalHits(); List<Facet> facetsResponse = jestRichResult.getFacets(); for (JestRichResult.Result h : jestRichResult.getHits()) { results.add(h.getObject(clazz)); } if (Logger.isDebugEnabled()) { Logger.debug("ElasticSearch : Results -> " + Joiner.on(",").join(results)); } long pageSize = 10; if (size > -1) { pageSize = size; } long pageCurrent = 1; if (from > 0) { pageCurrent = ((int) (from / pageSize)) + 1; } long pageNb; if (pageSize == 0) { pageNb = 1; } else { pageNb = (long) Math.ceil(new BigDecimal(count).divide(new BigDecimal(pageSize), 2, RoundingMode.HALF_UP).doubleValue()); } return new IndexResults<>(count, pageSize, pageCurrent, pageNb, results, facetsResponse); } }