package org.gbif.occurrence.search; import org.gbif.api.model.occurrence.search.OccurrenceSearchParameter; import org.gbif.api.model.occurrence.search.OccurrenceSearchRequest; import org.gbif.common.search.solr.QueryUtils; import org.gbif.common.search.solr.SolrConstants; import org.gbif.occurrence.search.solr.FacetField; import org.gbif.occurrence.search.solr.FacetFieldConfiguration; import org.gbif.occurrence.search.solr.OccurrenceSolrField; import org.gbif.occurrence.search.solr.SolrQueryUtils; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import org.apache.solr.client.solrj.SolrQuery; import static org.gbif.common.search.solr.QueryUtils.PARAMS_JOINER; import static org.gbif.common.search.solr.QueryUtils.PARAMS_OR_JOINER; import static org.gbif.common.search.solr.QueryUtils.setQueryPaging; import static org.gbif.common.search.solr.QueryUtils.setRequestHandler; import static org.gbif.common.search.solr.QueryUtils.setSortOrder; import static org.gbif.common.search.solr.QueryUtils.taggedField; import static org.gbif.common.search.solr.QueryUtils.toParenthesesQuery; import static org.gbif.common.search.solr.SearchDateUtils.toDateQuery; import static org.gbif.common.search.solr.SolrConstants.DEFAULT_QUERY; import static org.gbif.common.search.solr.SolrConstants.PARAM_OR_OP; /** * Utility class for building Solr queries from supported parameters for occurrences search. */ public class OccurrenceSearchRequestBuilder { private static final String SOLR_SPELLCHECK = "spellcheck"; private static final String SOLR_SPELLCHECK_COUNT = "spellcheck.count"; private static final String SOLR_SPELLCHECK_Q = "spellcheck.q"; private static final String DEFAULT_SPELL_CHECK_COUNT = "4"; private static final Pattern COMMON_REPLACER = Pattern.compile(",", Pattern.LITERAL); /** * Utility class to generates full text queries. */ private static final class OccurrenceFullTextQueryBuilder { private String q; private static final Double FUZZY_DISTANCE = 0.8; private static final String TERM_PATTERN = "%1$s^%2$s %1$s~%3$s^%4$s"; private static final String NON_TOKENIZED_QUERY_PATTERN = ":%1$s^1000"; private static final Set<String> NON_TOKENIZABLE_FIELDS = ImmutableSet.<String>of(OccurrenceSolrField.CATALOG_NUMBER.getFieldName() + NON_TOKENIZED_QUERY_PATTERN, OccurrenceSolrField.OCCURRENCE_ID.getFieldName() + NON_TOKENIZED_QUERY_PATTERN, OccurrenceSolrField.SCIENTIFIC_NAME.getFieldName() + NON_TOKENIZED_QUERY_PATTERN); private static final String NON_TOKENIZED_QUERY = PARAMS_OR_JOINER.join(NON_TOKENIZABLE_FIELDS); private static final Integer MAX_SCORE = 100; private static final Integer SCORE_DECREMENT = 20; /** * Query parameter. */ private OccurrenceFullTextQueryBuilder withQ(String q) { this.q = q; return this; } /** * Builds a Solr expression query with the form: "term1 ..termN" term1^100 term1~0.7^50 ... termN^20 termN~0.7^10. * Each boosting parameter is calculated using the formula: MAX_SCORE - SCORE_DECREMENT * i. Where 'i' is the * position of the term in the query. */ public String build() { String[] qs = q.split(" "); if(qs.length > 1) { StringBuilder ftQ = new StringBuilder(qs.length); String phraseQ = QueryUtils.toPhraseQuery(q); ftQ.append(phraseQ); ftQ.append(' '); for (int i = 0; i < qs.length; i++) { if (qs[i].length() > 1) { //ignore tokens of single letters int termScore = Math.max(MAX_SCORE - (SCORE_DECREMENT * i), SCORE_DECREMENT); ftQ.append(String.format(TERM_PATTERN, qs[i], termScore, FUZZY_DISTANCE, termScore / 2)); if (i < (qs.length - 1)) { ftQ.append(' '); } } } return PARAMS_OR_JOINER.join(String.format(NON_TOKENIZED_QUERY, phraseQ), ftQ.toString()); } return PARAMS_OR_JOINER.join(String.format(NON_TOKENIZED_QUERY, q), String.format(TERM_PATTERN, q, MAX_SCORE, FUZZY_DISTANCE, MAX_SCORE / 2)); } } // This is a placeholder to map from the JSON definition ID to the query field public static final ImmutableMap<OccurrenceSearchParameter, OccurrenceSolrField> QUERY_FIELD_MAPPING = ImmutableMap.<OccurrenceSearchParameter, OccurrenceSolrField>builder() .put(OccurrenceSearchParameter.DECIMAL_LATITUDE, OccurrenceSolrField.LATITUDE) .put(OccurrenceSearchParameter.DECIMAL_LONGITUDE, OccurrenceSolrField.LONGITUDE) .put(OccurrenceSearchParameter.YEAR, OccurrenceSolrField.YEAR) .put(OccurrenceSearchParameter.MONTH, OccurrenceSolrField.MONTH) .put(OccurrenceSearchParameter.CATALOG_NUMBER, OccurrenceSolrField.CATALOG_NUMBER) .put(OccurrenceSearchParameter.RECORDED_BY, OccurrenceSolrField.RECORDED_BY) .put(OccurrenceSearchParameter.RECORD_NUMBER, OccurrenceSolrField.RECORD_NUMBER) .put(OccurrenceSearchParameter.COLLECTION_CODE, OccurrenceSolrField.COLLECTION_CODE) .put(OccurrenceSearchParameter.INSTITUTION_CODE, OccurrenceSolrField.INSTITUTION_CODE) .put(OccurrenceSearchParameter.DEPTH, OccurrenceSolrField.DEPTH) .put(OccurrenceSearchParameter.ELEVATION, OccurrenceSolrField.ELEVATION) .put(OccurrenceSearchParameter.BASIS_OF_RECORD, OccurrenceSolrField.BASIS_OF_RECORD) .put(OccurrenceSearchParameter.DATASET_KEY, OccurrenceSolrField.DATASET_KEY) .put(OccurrenceSearchParameter.HAS_GEOSPATIAL_ISSUE, OccurrenceSolrField.SPATIAL_ISSUES) .put(OccurrenceSearchParameter.HAS_COORDINATE, OccurrenceSolrField.HAS_COORDINATE) .put(OccurrenceSearchParameter.EVENT_DATE, OccurrenceSolrField.EVENT_DATE) .put(OccurrenceSearchParameter.LAST_INTERPRETED, OccurrenceSolrField.LAST_INTERPRETED) .put(OccurrenceSearchParameter.COUNTRY, OccurrenceSolrField.COUNTRY) .put(OccurrenceSearchParameter.PUBLISHING_COUNTRY, OccurrenceSolrField.PUBLISHING_COUNTRY) .put(OccurrenceSearchParameter.CONTINENT, OccurrenceSolrField.CONTINENT) .put(OccurrenceSearchParameter.TAXON_KEY, OccurrenceSolrField.TAXON_KEY) .put(OccurrenceSearchParameter.KINGDOM_KEY, OccurrenceSolrField.KINGDOM_KEY) .put(OccurrenceSearchParameter.PHYLUM_KEY, OccurrenceSolrField.PHYLUM_KEY) .put(OccurrenceSearchParameter.CLASS_KEY, OccurrenceSolrField.CLASS_KEY) .put(OccurrenceSearchParameter.ORDER_KEY, OccurrenceSolrField.ORDER_KEY) .put(OccurrenceSearchParameter.FAMILY_KEY, OccurrenceSolrField.FAMILY_KEY) .put(OccurrenceSearchParameter.GENUS_KEY, OccurrenceSolrField.GENUS_KEY) .put(OccurrenceSearchParameter.SUBGENUS_KEY, OccurrenceSolrField.SUBGENUS_KEY) .put(OccurrenceSearchParameter.SPECIES_KEY, OccurrenceSolrField.SPECIES_KEY) .put(OccurrenceSearchParameter.SCIENTIFIC_NAME, OccurrenceSolrField.SCIENTIFIC_NAME) .put(OccurrenceSearchParameter.TYPE_STATUS, OccurrenceSolrField.TYPE_STATUS) .put(OccurrenceSearchParameter.MEDIA_TYPE, OccurrenceSolrField.MEDIA_TYPE) .put(OccurrenceSearchParameter.ISSUE, OccurrenceSolrField.ISSUE) .put(OccurrenceSearchParameter.OCCURRENCE_ID, OccurrenceSolrField.OCCURRENCE_ID) .put(OccurrenceSearchParameter.ESTABLISHMENT_MEANS, OccurrenceSolrField.ESTABLISHMENT_MEANS) .put(OccurrenceSearchParameter.REPATRIATED, OccurrenceSolrField.REPATRIATED) .put(OccurrenceSearchParameter.LOCALITY, OccurrenceSolrField.LOCALITY) .put(OccurrenceSearchParameter.STATE_PROVINCE, OccurrenceSolrField.STATE_PROVINCE) .put(OccurrenceSearchParameter.WATER_BODY, OccurrenceSolrField.WATER_BODY) .put(OccurrenceSearchParameter.LICENSE, OccurrenceSolrField.LICENSE) .put(OccurrenceSearchParameter.PROTOCOL, OccurrenceSolrField.PROTOCOL) .put(OccurrenceSearchParameter.ORGANISM_ID, OccurrenceSolrField.ORGANISM_ID) .put(OccurrenceSearchParameter.PUBLISHING_ORG, OccurrenceSolrField.PUBLISHING_ORGANIZATION_KEY) .put(OccurrenceSearchParameter.CRAWL_ID, OccurrenceSolrField.CRAWL_ID) .build(); public static final String GEO_INTERSECTS_QUERY_FMT = "\"IsWithin(%s) distErrPct=0\""; // Solr full text search handle private static final String FULL_TEXT_HANDLER = "/search"; // Holds the value used for an optional sort order applied to a search via param "sort" private final Map<String, SolrQuery.ORDER> sortOrder; // Solr request handler. private final String requestHandler; private final int maxOffset; private final int maxLimit; //Flag to enable/disable faceted search private final boolean facetsEnable; public static final int MAX_OFFSET = 1000000; public static final int MAX_PAGE_SIZE = 300; private static final Map<OccurrenceSearchParameter,FacetFieldConfiguration> FACET_FIELD_CONFIGURATION_MAP = getFacetsConfiguration(); /** * Default constructor. */ public OccurrenceSearchRequestBuilder(String requestHandler, Map<String, SolrQuery.ORDER> sortOrder, int maxOffset, int maxLimit, boolean facetsEnable) { Preconditions.checkArgument(maxOffset > 0, "Max offset must be greater than zero"); Preconditions.checkArgument(maxLimit > 0, "Max limit must be greater than zero"); this.requestHandler = requestHandler; this.sortOrder = sortOrder; this.maxOffset = Math.min(maxOffset, MAX_OFFSET); this.maxLimit = Math.min(maxLimit, MAX_PAGE_SIZE); this.facetsEnable = facetsEnable; } /** * Parses a geometry parameter in WKT format. * If the parsed geometry is a polygon the produced query will be in INTERSECTS(wkt parameter) format. * If the parsed geometry is a rectangle, the query is transformed into a range query using the southmost and * northmost points. */ protected static String parseGeometryParam(String wkt) { try { Geometry geometry = new WKTReader().read(wkt); return String.format(GEO_INTERSECTS_QUERY_FMT, geometry.toText()); } catch (ParseException e) { throw new IllegalArgumentException(e); } } public SolrQuery build(@Nullable OccurrenceSearchRequest request) { Preconditions.checkArgument(request.getOffset() <= maxOffset - request.getLimit(), "maximum offset allowed is %s", this.maxOffset); SolrQuery solrQuery = new SolrQuery(); solrQuery.setParam(SOLR_SPELLCHECK, request.isSpellCheck()); if (request.isSpellCheck()) { solrQuery.setParam(SOLR_SPELLCHECK_COUNT, request.getSpellCheckCount() < 0 ? DEFAULT_SPELL_CHECK_COUNT : Integer.toString(request.getSpellCheckCount())); } // set the request handler setRequestHandler(solrQuery, requestHandler); // q param if (Strings.isNullOrEmpty(request.getQ()) || SolrConstants.DEFAULT_FILTER_QUERY.equals(request.getQ())) { solrQuery.setQuery(DEFAULT_QUERY); solrQuery.setParam(SOLR_SPELLCHECK, Boolean.FALSE); // sorting is set only when the q parameter is empty, otherwise the score value es used setSortOrder(solrQuery, sortOrder); } else { OccurrenceFullTextQueryBuilder occurrenceFullTextQueryBuilder = new OccurrenceFullTextQueryBuilder(); occurrenceFullTextQueryBuilder.withQ(request.getQ()); solrQuery.setQuery(occurrenceFullTextQueryBuilder.build()); solrQuery.setParam(SOLR_SPELLCHECK_Q, request.getQ()); solrQuery.setRequestHandler(FULL_TEXT_HANDLER); } // paging setQueryPaging(request, solrQuery, maxLimit); // sets the filters setFilterParameters(request, solrQuery, facetsEnable); if (facetsEnable) { SolrQueryUtils.applyFacetSettings(request, solrQuery, FACET_FIELD_CONFIGURATION_MAP); solrQuery.setFacetMissing(false); } return solrQuery; } private static Map<OccurrenceSearchParameter, FacetFieldConfiguration> getFacetsConfiguration() { return ImmutableMap.<OccurrenceSearchParameter, FacetFieldConfiguration>builder() .put(OccurrenceSearchParameter.BASIS_OF_RECORD, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.BASIS_OF_RECORD).getFieldName(), OccurrenceSearchParameter.BASIS_OF_RECORD, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.TYPE_STATUS, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.TYPE_STATUS).getFieldName(), OccurrenceSearchParameter.TYPE_STATUS, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.DATASET_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.DATASET_KEY).getFieldName(), OccurrenceSearchParameter.DATASET_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.TAXON_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.TAXON_KEY).getFieldName(), OccurrenceSearchParameter.TAXON_KEY, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.COUNTRY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.COUNTRY).getFieldName(), OccurrenceSearchParameter.COUNTRY, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.MONTH, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.MONTH).getFieldName(), OccurrenceSearchParameter.MONTH, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.YEAR, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.YEAR).getFieldName(), OccurrenceSearchParameter.YEAR, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.KINGDOM_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.KINGDOM_KEY).getFieldName(), OccurrenceSearchParameter.KINGDOM_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.PHYLUM_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.PHYLUM_KEY).getFieldName(), OccurrenceSearchParameter.PHYLUM_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.CLASS_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.CLASS_KEY).getFieldName(), OccurrenceSearchParameter.CLASS_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.ORDER_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.ORDER_KEY).getFieldName(), OccurrenceSearchParameter.ORDER_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.FAMILY_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.FAMILY_KEY).getFieldName(), OccurrenceSearchParameter.FAMILY_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.GENUS_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.GENUS_KEY).getFieldName(), OccurrenceSearchParameter.GENUS_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.SUBGENUS_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.SUBGENUS_KEY).getFieldName(), OccurrenceSearchParameter.SUBGENUS_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.SPECIES_KEY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.SPECIES_KEY).getFieldName(), OccurrenceSearchParameter.SPECIES_KEY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.REPATRIATED, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.REPATRIATED).getFieldName(), OccurrenceSearchParameter.REPATRIATED, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.HAS_COORDINATE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.HAS_COORDINATE).getFieldName(), OccurrenceSearchParameter.HAS_COORDINATE, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.HAS_GEOSPATIAL_ISSUE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.HAS_GEOSPATIAL_ISSUE).getFieldName(), OccurrenceSearchParameter.HAS_GEOSPATIAL_ISSUE, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.CATALOG_NUMBER, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.CATALOG_NUMBER).getFieldName(), OccurrenceSearchParameter.CATALOG_NUMBER, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.COLLECTION_CODE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.COLLECTION_CODE).getFieldName(), OccurrenceSearchParameter.COLLECTION_CODE, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.CONTINENT, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.CONTINENT).getFieldName(), OccurrenceSearchParameter.CONTINENT, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.DECIMAL_LATITUDE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.DECIMAL_LATITUDE).getFieldName(), OccurrenceSearchParameter.DECIMAL_LATITUDE, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.DECIMAL_LONGITUDE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.DECIMAL_LONGITUDE).getFieldName(), OccurrenceSearchParameter.DECIMAL_LONGITUDE, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.RECORDED_BY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.RECORDED_BY).getFieldName(), OccurrenceSearchParameter.RECORDED_BY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.RECORD_NUMBER, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.RECORD_NUMBER).getFieldName(), OccurrenceSearchParameter.RECORD_NUMBER, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.PUBLISHING_COUNTRY, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.PUBLISHING_COUNTRY).getFieldName(), OccurrenceSearchParameter.PUBLISHING_COUNTRY, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.DEPTH, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.DEPTH).getFieldName(), OccurrenceSearchParameter.DEPTH, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.ELEVATION, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.ELEVATION).getFieldName(), OccurrenceSearchParameter.ELEVATION, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.OCCURRENCE_ID, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.OCCURRENCE_ID).getFieldName(), OccurrenceSearchParameter.OCCURRENCE_ID, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.MEDIA_TYPE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.MEDIA_TYPE).getFieldName(), OccurrenceSearchParameter.MEDIA_TYPE, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.ESTABLISHMENT_MEANS, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.ESTABLISHMENT_MEANS).getFieldName(), OccurrenceSearchParameter.ESTABLISHMENT_MEANS, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.ISSUE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.ISSUE).getFieldName(), OccurrenceSearchParameter.ISSUE, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.INSTITUTION_CODE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.INSTITUTION_CODE).getFieldName(), OccurrenceSearchParameter.INSTITUTION_CODE, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.SCIENTIFIC_NAME, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.SCIENTIFIC_NAME).getFieldName(), OccurrenceSearchParameter.SCIENTIFIC_NAME, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.PROTOCOL, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.PROTOCOL).getFieldName(), OccurrenceSearchParameter.PROTOCOL, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.LICENSE, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.LICENSE).getFieldName(), OccurrenceSearchParameter.LICENSE, FacetField.Method.ENUM, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.CRAWL_ID, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.CRAWL_ID).getFieldName(), OccurrenceSearchParameter.CRAWL_ID, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .put(OccurrenceSearchParameter.PUBLISHING_ORG, new FacetFieldConfiguration(QUERY_FIELD_MAPPING.get(OccurrenceSearchParameter.PUBLISHING_ORG).getFieldName(), OccurrenceSearchParameter.PUBLISHING_ORG, FacetField.Method.FIELD_CACHE, FacetField.SortOrder.COUNT, false)) .build(); } /** * Adds an occurrence date parameter: DATE or MODIFIED. */ private static void addDateQuery(Multimap<OccurrenceSearchParameter, String> params, OccurrenceSearchParameter dateParam, OccurrenceSolrField solrField, SolrQuery solrQuery, boolean isFacetedSearch) { if (params.containsKey(dateParam)) { String dateParams = params.get(dateParam).stream() .map(value -> PARAMS_JOINER.join(solrField.getFieldName(), toDateQuery(value))) .collect(Collectors.joining(PARAM_OR_OP)); solrQuery.addFilterQuery((isFacetedSearch ? taggedField(solrField.getFieldName()) : "") + toParenthesesQuery(dateParams)); } } /** * Add the occurrence bounding box and polygon parameters. * Those 2 parameters are returned in 1 filter expression because both refer to same Solr field: coordinate. */ private static void addLocationQuery(Multimap<OccurrenceSearchParameter, String> params, SolrQuery solrQuery, boolean isFacetedSearch) { if (params.containsKey(OccurrenceSearchParameter.GEOMETRY)) { String locationParams = params.get(OccurrenceSearchParameter.GEOMETRY).stream() .map(value -> PARAMS_JOINER.join(OccurrenceSolrField.COORDINATE.getFieldName(), parseGeometryParam(value))) .collect(Collectors.joining(PARAM_OR_OP)); solrQuery.addFilterQuery((isFacetedSearch ? taggedField(OccurrenceSolrField.COORDINATE.getFieldName()) : "") + toParenthesesQuery(locationParams)); } } /** * Adds the filter query to SolrQuery object. * Creates a conjunction of disjunctions: disjunctions(ORs) are created for the filter applied to the same field; * those disjunctions are joint in a big conjunction. */ private static void setFilterParameters(OccurrenceSearchRequest request, SolrQuery solrQuery, boolean facetsEnable) { Multimap<OccurrenceSearchParameter, String> params = request.getParameters(); boolean isFacetedSearch = facetsEnable && request.getFacets() != null && !request.getFacets().isEmpty(); if (params != null && !params.isEmpty()) { for (OccurrenceSearchParameter param : params.keySet()) { OccurrenceSolrField solrField = QUERY_FIELD_MAPPING.get(param); if (solrField != null) { Collection<String> paramValues = params.get(param); List<String> aFieldParameters = Lists.newArrayListWithExpectedSize(paramValues.size()); for (String value : paramValues) { if (param.type() != Date.class) { String parsedValue = QueryUtils.parseQueryValue(value); if (QueryUtils.isRangeQuery(parsedValue)) { parsedValue = COMMON_REPLACER.matcher(parsedValue).replaceAll(" TO "); } if (Enum.class.isAssignableFrom(param.type())) { // enums are capitalized parsedValue = parsedValue.toUpperCase(); } aFieldParameters.add(PARAMS_JOINER.join(solrField.getFieldName(), parsedValue)); } } if (!aFieldParameters.isEmpty()) { solrQuery.addFilterQuery((isFacetedSearch ? taggedField(solrField.getFieldName()) : "") + toParenthesesQuery(PARAMS_OR_JOINER.join(aFieldParameters))); } } } addLocationQuery(params, solrQuery, isFacetedSearch); addDateQuery(params, OccurrenceSearchParameter.EVENT_DATE, OccurrenceSolrField.EVENT_DATE, solrQuery, isFacetedSearch); addDateQuery(params, OccurrenceSearchParameter.LAST_INTERPRETED, OccurrenceSolrField.LAST_INTERPRETED, solrQuery, isFacetedSearch); } } public boolean isFacetsEnable() { return facetsEnable; } }