Prashanth Raghu
Prashanth Raghu

Reputation: 473

Performing faceted search with elastic search repositories using spring data?

I am in the need of performing faceted search using elastic search repositories developed using spring data.

One of the repositories which I have created are

public interface EmployeeSearchRepository extends ElasticsearchRepository<Employee, Long> { }

it does provide a method called search with a signature:

FacetedPage<Employee> search(QueryBuilder query, Pageable pageable);

but the getFacets method of the FacetedPage returns null. How can I query to generate the facets?

Upvotes: 2

Views: 1625

Answers (1)

O.Chougna
O.Chougna

Reputation: 21

I have the same problem and it seems that it is not implemented (yet). If you look at DefaultResultMapper.mapResults() it calls response.getFacets() which is always null.

Note that facets are deprecated in elasticsearch and you should use aggregations instead. So maybe the contributors of the project are refactoring it?

I worked this around by writing my own results mapper class which extends the DefaultResultMapper but also converts the aggregations to FacetResults.

SomethingResultsMapper:

@Override
    public <T> FacetedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
        FacetedPage<T> facetedPage = super.mapResults(response, clazz, pageable);
        //Process Aggregations.
        if (response.getAggregations() != null) {
            for (Aggregation aggregations : response.getAggregations().asList()) {

                final Filter filterAggregations = (Filter) aggregations;
                for (Aggregation filterAgg : filterAggregations.getAggregations().asList()) {
                    if (filterAgg instanceof Terms) {
                        final Terms aggTerm = (Terms) filterAgg;
                        if (!aggTerm.getBuckets().isEmpty()) {
                            facetedPage.getFacets().add(processTermAggregation(aggTerm));
                        }
                    } else if (filterAgg instanceof Nested) {
                        final Nested nestedAgg = (Nested) filterAgg;
                        for (Aggregation aggregation : nestedAgg.getAggregations().asList()) {
                            final Terms aggTerm = (Terms) aggregation;
                            if (!aggTerm.getBuckets().isEmpty()) {
                                facetedPage.getFacets().add(processTermAggregation(aggTerm));
                            }
                        }
                    } else {
                        throw new IllegalArgumentException("Aggregation type not (yet) supported: " + filterAgg.getClass().getName());
                    }
                }
            }
        }
        return facetedPage;
    }

    private FacetResult processTermAggregation(final Terms aggTerm) {
        long total = 0;
        List<Term> terms = new ArrayList<>();
        List<Terms.Bucket> buckets = aggTerm.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            terms.add(new Term(bucket.getKey(), (int) bucket.getDocCount()));
            total += bucket.getDocCount();
        }
        return new FacetTermResult(aggTerm.getName(), FacetConfig.fromAggregationTerm(aggTerm.getName()).getLabel(),
                terms, total, aggTerm.getSumOfOtherDocCounts(), aggTerm.getDocCountError());
    }

Then i created a custom Spring data repository (see the docs) and defined a custom method where i provide my SomethingResultsMapper:

@Override
public FacetedPage<Something> searchSomething(final SearchQuery searchQuery) {
    return elasticsearchTemplate.queryForPage(searchQuery, Something.class, new SomethingResultsMapper());
}

EDIT: I think this one is being fixed by https://jira.spring.io/browse/DATAES-211

Upvotes: 1

Related Questions