package org.gbif.checklistbank.index.service; import org.gbif.api.model.checklistbank.Description; import org.gbif.api.model.checklistbank.VernacularName; import org.gbif.api.model.checklistbank.search.NameUsageSearchParameter; import org.gbif.api.model.checklistbank.search.NameUsageSearchRequest; import org.gbif.api.model.checklistbank.search.NameUsageSearchResult; import org.gbif.api.model.checklistbank.search.NameUsageSuggestResult; import org.gbif.api.model.common.search.Facet; import org.gbif.api.model.common.search.SearchResponse; import org.gbif.checklistbank.index.NameUsageDocConverter; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import static org.gbif.common.search.solr.SolrConstants.HL_POST; import static org.gbif.common.search.solr.SolrConstants.HL_PRE; import static org.gbif.common.search.solr.SolrConstants.HL_PRE_REGEX; /** * */ public class ResponseBuilder { private final NameUsageDocConverter converter = new NameUsageDocConverter(); /** * Builds a SearchResponse instance using the current builder state. * * @return a new instance of a SearchResponse. */ public SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> buildSearch(NameUsageSearchRequest searchRequest, QueryResponse response) { // Create response SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> resp = new SearchResponse<NameUsageSearchResult, NameUsageSearchParameter>(searchRequest); resp.setCount(response.getResults().getNumFound()); resp.setLimit(response.getResults().size()); // Main result documents SolrDocumentList docs = response.getResults(); for (SolrDocument doc : docs) { resp.getResults().add(converter.toSearchUsage(doc, searchRequest.isExtended())); } // add facets setFacets(resp, response); // add highlighting setHighlighting(resp, response, searchRequest); return resp; } /** * Builds a SearchResponse instance using the current builder state. * * @return a new instance of a SearchResponse. */ public List<NameUsageSuggestResult> buildSuggest(QueryResponse response) { List<NameUsageSuggestResult> result = Lists.newArrayList(); // Main result documents SolrDocumentList docs = response.getResults(); for (SolrDocument doc : docs) { result.add(converter.toSuggestUsage(doc)); } return result; } /** * Helper method that takes Solr response and extracts the facets results. * The facets are converted to a list of Facets understood by the search API. * The result of this method can be a empty list. * * @param queryResponse that contains the facets information returned by Solr * @return the List of facets retrieved from the Solr response */ private void setFacets(SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> response, final QueryResponse queryResponse) { List<Facet<NameUsageSearchParameter>> facets = Lists.newArrayList(); if (queryResponse.getFacetFields() != null) { List<FacetField> facetFields = queryResponse.getFacetFields(); for (final FacetField facetField : facetFields) { NameUsageSearchParameter facetParam = SolrMapping.FACET_MAPPING.inverse().get(facetField.getName()); Facet<NameUsageSearchParameter> facet = new Facet<NameUsageSearchParameter>(facetParam); List<Facet.Count> counts = Lists.newArrayList(); if (facetField.getValues() != null) { for (final FacetField.Count count : facetField.getValues()) { String value = SolrMapping.interpretSolrValue(facetParam, count.getName()); counts.add(new Facet.Count(value, count.getCount())); } } facet.setCounts(counts); facets.add(facet); } } response.setFacets(facets); } /** * Takes the highlighted fields form solrResponse and copies them to the response object. * @param response to set the highlighted fields. * @param solrResponse to extract the highlighting information * @param request the search request */ private void setHighlighting(SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> response, final QueryResponse solrResponse, NameUsageSearchRequest request) { if ((solrResponse.getHighlighting() != null) && !solrResponse.getHighlighting().isEmpty()) { for (String docId : solrResponse.getHighlighting().keySet()) { NameUsageSearchResult bean = getByKey(response, docId); if (bean != null) { Map<String, List<String>> docHighlights = solrResponse.getHighlighting().get(docId); for (NameUsageSearchRequest.QueryField hlField : request.getHighlightFields()) { if (docHighlights.containsKey(SolrMapping.HIGHLIGHT_FIELDS.get(hlField))) { for (String hlSnippet : docHighlights.get(SolrMapping.HIGHLIGHT_FIELDS.get(hlField))) { if (request.isExtended()) { // merge highlighting into existing results switch (hlField) { case DESCRIPTION: for (Description d : bean.getDescriptions()) { d.setDescription(mergeHl(d.getDescription(), hlSnippet)); } break; case VERNACULAR: for (VernacularName v : bean.getVernacularNames()) { v.setVernacularName(mergeHl(v.getVernacularName(), hlSnippet)); } break; } } else { // just add highlighted results switch (hlField) { case DESCRIPTION: Description d = new Description(); d.setDescription(hlSnippet); bean.getDescriptions().add(d); break; case VERNACULAR: VernacularName v = new VernacularName(); v.setVernacularName(hlSnippet); bean.getVernacularNames().add(v); break; } } } } } } } } } @VisibleForTesting protected static String mergeHl(String original, String hlSnippet) { // Cleans the hl markers String hlCleaned = cleanHighlightingMarks(hlSnippet); // replace snippet in original return original.replace(hlCleaned, hlSnippet); } /** * Cleans all occurrences of highlighted tags/marks in the parameter and returns an new instance clean of those * marks. */ private static String cleanHighlightingMarks(final String hlText) { String hlLiteral = hlText; int indexPre = hlLiteral.indexOf(HL_PRE); while (indexPre > -1) { int indexPost = hlLiteral.indexOf(HL_POST, indexPre + HL_PRE.length()); if (indexPost > -1) { String post = hlLiteral.substring(indexPost + HL_POST.length()); String pre = hlLiteral.substring(0, indexPost); Matcher preMatcher = HL_PRE_REGEX.matcher(pre); pre = preMatcher.replaceFirst(""); hlLiteral = pre + post; } indexPre = hlLiteral.indexOf(HL_PRE); } return hlLiteral; } private NameUsageSearchResult getByKey(SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> response, String key) { for (NameUsageSearchResult bean : response.getResults()) { if (bean.getKey().toString().equals(key)) { return bean; } } return null; } }