/* * Copyright 2012 - 2017 the original author or authors. * * 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 org.springframework.data.solr.core; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.FacetField.Count; import org.apache.solr.client.solrj.response.FieldStatsInfo; import org.apache.solr.client.solrj.response.Group; import org.apache.solr.client.solrj.response.GroupCommand; import org.apache.solr.client.solrj.response.GroupResponse; import org.apache.solr.client.solrj.response.PivotField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.RangeFacet; import org.apache.solr.client.solrj.response.SpellCheckResponse; import org.apache.solr.client.solrj.response.TermsResponse; import org.apache.solr.client.solrj.response.TermsResponse.Term; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.util.NamedList; import org.springframework.beans.DirectFieldAccessor; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.repository.util.ClassUtils; import org.springframework.data.solr.core.query.FacetQuery; import org.springframework.data.solr.core.query.Field; import org.springframework.data.solr.core.query.Query; import org.springframework.data.solr.core.query.SimpleField; import org.springframework.data.solr.core.query.SimplePivotField; import org.springframework.data.solr.core.query.result.FacetFieldEntry; import org.springframework.data.solr.core.query.result.FacetPivotFieldEntry; import org.springframework.data.solr.core.query.result.FacetQueryEntry; import org.springframework.data.solr.core.query.result.FieldStatsResult; import org.springframework.data.solr.core.query.result.GroupEntry; import org.springframework.data.solr.core.query.result.GroupResult; import org.springframework.data.solr.core.query.result.HighlightEntry; import org.springframework.data.solr.core.query.result.SimpleFacetFieldEntry; import org.springframework.data.solr.core.query.result.SimpleFacetPivotEntry; import org.springframework.data.solr.core.query.result.SimpleFacetQueryEntry; import org.springframework.data.solr.core.query.result.SimpleFieldStatsResult; import org.springframework.data.solr.core.query.result.SimpleGroupEntry; import org.springframework.data.solr.core.query.result.SimpleGroupResult; import org.springframework.data.solr.core.query.result.SimpleStatsResult; import org.springframework.data.solr.core.query.result.SimpleTermsFieldEntry; import org.springframework.data.solr.core.query.result.SolrResultPage; import org.springframework.data.solr.core.query.result.SpellcheckQueryResult.Alternative; import org.springframework.data.solr.core.query.result.StatsResult; import org.springframework.data.solr.core.query.result.TermsFieldEntry; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * Use Result Helper to extract various parameters from the QueryResponse and convert it into a proper Format taking * care of non existent and null elements with the response. * * @author Christoph Strobl * @author Francisco Spaeth * @author Venil Noronha */ final class ResultHelper { private ResultHelper() {} static Map<String, List<TermsFieldEntry>> convertTermsQueryResponseToTermsMap(QueryResponse response) { if (response == null || response.getTermsResponse() == null || response.getTermsResponse().getTermMap() == null) { return Collections.emptyMap(); } TermsResponse termsResponse = response.getTermsResponse(); Map<String, List<TermsFieldEntry>> result = new LinkedHashMap<>(termsResponse.getTermMap().size()); for (Map.Entry<String, List<Term>> entry : termsResponse.getTermMap().entrySet()) { List<TermsFieldEntry> terms = new ArrayList<>(entry.getValue().size()); for (Term term : entry.getValue()) { SimpleTermsFieldEntry termsEntry = new SimpleTermsFieldEntry(term.getTerm(), term.getFrequency()); termsEntry.setField(entry.getKey()); terms.add(termsEntry); } result.put(entry.getKey(), terms); } return result; } static Map<Field, Page<FacetFieldEntry>> convertFacetQueryResponseToFacetPageMap(FacetQuery query, QueryResponse response) { Assert.notNull(query, "Cannot convert response for 'null', query"); if (!hasFacets(query, response)) { return Collections.emptyMap(); } Map<Field, Page<FacetFieldEntry>> facetResult = new LinkedHashMap<>(); if (!CollectionUtils.isEmpty(response.getFacetFields())) { int initalPageSize = Math.max(1, query.getFacetOptions().getPageable().getPageSize()); for (FacetField facetField : response.getFacetFields()) { if (facetField != null && StringUtils.hasText(facetField.getName())) { Field field = new SimpleField(facetField.getName()); if (!CollectionUtils.isEmpty(facetField.getValues())) { List<FacetFieldEntry> pageEntries = new ArrayList<>(initalPageSize); for (Count count : facetField.getValues()) { if (count != null) { pageEntries.add(new SimpleFacetFieldEntry(field, count.getName(), count.getCount())); } } facetResult.put(field, new SolrResultPage<>(pageEntries, query.getFacetOptions().getPageable(), facetField.getValueCount(), null)); } else { facetResult.put(field, new SolrResultPage<>(Collections.<FacetFieldEntry> emptyList(), query.getFacetOptions().getPageable(), 0, null)); } } } } return facetResult; } static Map<org.springframework.data.solr.core.query.PivotField, List<FacetPivotFieldEntry>> convertFacetQueryResponseToFacetPivotMap( FacetQuery query, QueryResponse response) { Map<org.springframework.data.solr.core.query.PivotField, List<FacetPivotFieldEntry>> facetResult = new LinkedHashMap<>(); NamedList<List<PivotField>> facetPivot = response.getFacetPivot(); if (facetPivot != null && facetPivot.size() > 0) { for (int i = 0; i < facetPivot.size(); i++) { String name = facetPivot.getName(i); List<PivotField> pivotResult = facetPivot.get(name); facetResult.put(new SimplePivotField(name), convertPivotResult(pivotResult)); } } return facetResult; } private static List<FacetPivotFieldEntry> convertPivotResult(List<PivotField> pivotResult) { if (CollectionUtils.isEmpty(pivotResult)) { return Collections.emptyList(); } ArrayList<FacetPivotFieldEntry> pivotFieldEntries = new ArrayList<>(); for (PivotField pivotField : pivotResult) { SimpleFacetPivotEntry pivotFieldEntry = new SimpleFacetPivotEntry(new SimpleField(pivotField.getField()), String.valueOf(pivotField.getValue()), pivotField.getCount()); List<PivotField> pivot = pivotField.getPivot(); if (pivot != null) { pivotFieldEntry.setPivot(convertPivotResult(pivot)); } pivotFieldEntries.add(pivotFieldEntry); } return pivotFieldEntries; } /** * @param query * @param response * @return * @since 1.5 */ static Map<Field, Page<FacetFieldEntry>> convertFacetQueryResponseToRangeFacetPageMap(FacetQuery query, QueryResponse response) { Assert.notNull(query, "Cannot convert response for 'null', query"); if (!hasFacets(query, response) || CollectionUtils.isEmpty(response.getFacetRanges())) { return Collections.emptyMap(); } Map<Field, Page<FacetFieldEntry>> facetResult = new LinkedHashMap<>(); Pageable pageable = query.getFacetOptions().getPageable(); int initalPageSize = pageable.getPageSize(); for (RangeFacet<?, ?> rangeFacet : response.getFacetRanges()) { if (rangeFacet == null || !StringUtils.hasText(rangeFacet.getName())) { continue; } Field field = new SimpleField(rangeFacet.getName()); List<FacetFieldEntry> entries; long total; if (!CollectionUtils.isEmpty(rangeFacet.getCounts())) { entries = new ArrayList<>(initalPageSize); for (RangeFacet.Count count : rangeFacet.getCounts()) { entries.add(new SimpleFacetFieldEntry(field, count.getValue(), count.getCount())); } total = rangeFacet.getCounts().size(); } else { entries = Collections.<FacetFieldEntry> emptyList(); total = 0; } facetResult.put(field, new SolrResultPage<>(entries, pageable, total, null)); } return facetResult; } static List<FacetQueryEntry> convertFacetQueryResponseToFacetQueryResult(FacetQuery query, QueryResponse response) { Assert.notNull(query, "Cannot convert response for 'null', query"); if (!hasFacets(query, response)) { return Collections.emptyList(); } List<FacetQueryEntry> facetResult = new ArrayList<>(); if (!CollectionUtils.isEmpty(response.getFacetQuery())) { for (Entry<String, Integer> entry : response.getFacetQuery().entrySet()) { facetResult.add(new SimpleFacetQueryEntry(entry.getKey(), entry.getValue())); } } return facetResult; } static <T> List<HighlightEntry<T>> convertAndAddHighlightQueryResponseToResultPage(QueryResponse response, SolrResultPage<T> page) { if (response == null || CollectionUtils.isEmpty(response.getHighlighting()) || page == null) { return Collections.emptyList(); } List<HighlightEntry<T>> mappedHighlights = new ArrayList<>(page.getSize()); Map<String, Map<String, List<String>>> highlighting = response.getHighlighting(); for (T item : page) { HighlightEntry<T> highlightEntry = processHighlightingForPageEntry(highlighting, item); mappedHighlights.add(highlightEntry); } page.setHighlighted(mappedHighlights); return mappedHighlights; } private static <T> HighlightEntry<T> processHighlightingForPageEntry( Map<String, Map<String, List<String>>> highlighting, T pageEntry) { HighlightEntry<T> highlightEntry = new HighlightEntry<>(pageEntry); Object itemId = getMappedId(pageEntry); Map<String, List<String>> highlights = highlighting.get(itemId.toString()); if (!CollectionUtils.isEmpty(highlights)) { for (Map.Entry<String, List<String>> entry : highlights.entrySet()) { highlightEntry.addSnipplets(entry.getKey(), entry.getValue()); } } return highlightEntry; } private static Object getMappedId(Object o) { if (ClassUtils.hasProperty(o.getClass(), "id")) { try { return FieldUtils.readDeclaredField(o, "id", true); } catch (IllegalAccessException e) { throw new MappingException("Id property could not be accessed!", e); } } for (java.lang.reflect.Field field : o.getClass().getDeclaredFields()) { Annotation annotation = AnnotationUtils.getAnnotation(field, Id.class); if (annotation != null) { try { return FieldUtils.readField(field, o, true); } catch (IllegalArgumentException | IllegalAccessException e) { throw new MappingException("Id property could not be accessed!", e); } } } throw new MappingException("Id property could not be found!"); } private static boolean hasFacets(FacetQuery query, QueryResponse response) { return query.hasFacetOptions() && response != null; } static <T> Map<Object, GroupResult<T>> convertGroupQueryResponseToGroupResultMap(Query query, Map<String, Object> objectNames, QueryResponse response, SolrTemplate solrTemplate, Class<T> clazz) { GroupResponse groupResponse = response.getGroupResponse(); if (groupResponse == null) { return Collections.emptyMap(); } Map<Object, GroupResult<T>> result = new LinkedHashMap<>(); List<GroupCommand> values = groupResponse.getValues(); for (GroupCommand groupCommand : values) { List<GroupEntry<T>> groupEntries = new ArrayList<>(); for (Group group : groupCommand.getValues()) { SolrDocumentList documentList = group.getResult(); List<T> beans = solrTemplate.convertSolrDocumentListToBeans(documentList, clazz); Page<T> page = new PageImpl<>(beans, query.getGroupOptions().getPageRequest(), documentList.getNumFound()); groupEntries.add(new SimpleGroupEntry<>(group.getGroupValue(), page)); } int matches = groupCommand.getMatches(); Integer ngroups = groupCommand.getNGroups(); String name = groupCommand.getName(); PageImpl<GroupEntry<T>> page; if (ngroups != null) { page = new PageImpl<>(groupEntries, query.getPageRequest(), ngroups); } else { page = new PageImpl<>(groupEntries); } SimpleGroupResult<T> groupResult = new SimpleGroupResult<>(matches, ngroups, name, page); result.put(name, groupResult); if (objectNames.containsKey(name)) { result.put(objectNames.get(name), groupResult); } } return result; } static Map<String, FieldStatsResult> convertFieldStatsInfoToFieldStatsResultMap( Map<String, FieldStatsInfo> fieldStatsInfo) { if (fieldStatsInfo == null) { return Collections.emptyMap(); } Map<String, FieldStatsResult> result = new LinkedHashMap<>(); for (Entry<String, FieldStatsInfo> entry : fieldStatsInfo.entrySet()) { FieldStatsInfo value = entry.getValue(); if (value == null) { result.put(entry.getKey(), new SimpleFieldStatsResult()); continue; } SimpleFieldStatsResult statsResult = populateStatsResultWithFieldStatsInfo(new SimpleFieldStatsResult(), value); statsResult.setCountDistinct(value.getCountDistinct()); statsResult.setDistinctValues(value.getDistinctValues()); Map<String, List<FieldStatsInfo>> facets = value.getFacets(); if (facets != null) { statsResult.setStatsResults(convertFieldStatsInfoToStatsResultMap(facets)); } result.put(entry.getKey(), statsResult); } return result; } private static Map<String, Map<String, StatsResult>> convertFieldStatsInfoToStatsResultMap( Map<String, List<FieldStatsInfo>> statsInfo) { Map<String, Map<String, StatsResult>> result = new LinkedHashMap<>(); for (Entry<String, List<FieldStatsInfo>> entry : statsInfo.entrySet()) { Map<String, StatsResult> facetStatsValues = new LinkedHashMap<>(); for (FieldStatsInfo fieldStatsInfo : entry.getValue()) { SimpleStatsResult statsResult = populateStatsResultWithFieldStatsInfo(new SimpleStatsResult(), fieldStatsInfo); facetStatsValues.put(fieldStatsInfo.getName(), statsResult); } result.put(entry.getKey(), facetStatsValues); } return result; } private static <T extends SimpleStatsResult> T populateStatsResultWithFieldStatsInfo(T statsResult, FieldStatsInfo value) { statsResult.setMax(value.getMax()); statsResult.setMin(value.getMin()); statsResult.setCount(value.getCount()); statsResult.setMissing(value.getMissing()); statsResult.setStddev(value.getStddev()); statsResult.setSumOfSquares((Double) new DirectFieldAccessor(value).getPropertyValue("sumOfSquares")); Object mean = value.getMean(); if (mean instanceof Double) { statsResult.setMean((Double) mean); } Object sum = value.getSum(); if (sum instanceof Double) { statsResult.setSum((Double) sum); } return statsResult; } static Map<String, List<Alternative>> extreactSuggestions(QueryResponse response) { if (response == null || response.getSpellCheckResponse() == null || response.getSpellCheckResponse().getSuggestions() == null) { return Collections.emptyMap(); } Map<String, List<Alternative>> alternativesMap = new LinkedHashMap<>(); SpellCheckResponse scr = response.getSpellCheckResponse(); if (scr != null && scr.getSuggestions() != null) { for (SpellCheckResponse.Suggestion suggestion : scr.getSuggestions()) { List<Alternative> alternatives = new ArrayList<>(); if (!CollectionUtils.isEmpty(suggestion.getAlternatives())) { for (int i = 0; i < suggestion.getAlternatives().size(); i++) { alternatives.add(new Alternative(suggestion.getToken(), suggestion.getOriginalFrequency(), suggestion.getAlternatives().get(i), suggestion.getAlternativeFrequencies().get(i))); } } alternativesMap.put(suggestion.getToken(), alternatives); } } return alternativesMap; } }