org.springframework.data.solr.core.DefaultQueryParser.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.solr.core.DefaultQueryParser.java

Source

/*
 * Copyright 2012 - 2018 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
 *
 * https://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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.GroupParams;
import org.apache.solr.common.params.HighlightParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SpellingParams;
import org.apache.solr.common.params.StatsParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.solr.core.query.*;
import org.springframework.data.solr.core.query.Criteria.Predicate;
import org.springframework.data.solr.core.query.FacetOptions.FacetParameter;
import org.springframework.data.solr.core.query.FacetOptions.FieldWithDateRangeParameters;
import org.springframework.data.solr.core.query.FacetOptions.FieldWithFacetParameters;
import org.springframework.data.solr.core.query.FacetOptions.FieldWithNumericRangeParameters;
import org.springframework.data.solr.core.query.FacetOptions.FieldWithRangeParameters;
import org.springframework.data.solr.core.query.Function.Context.Target;
import org.springframework.data.solr.core.query.HighlightOptions.FieldWithHighlightParameters;
import org.springframework.data.solr.core.query.HighlightOptions.HighlightParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

/**
 * Implementation of {@link QueryParser}. <br/>
 * Creates executable {@link SolrQuery} from {@link Query} by traversing {@link Criteria}. Reserved characters like
 * {@code +} or {@code -} will be escaped to form a valid query.
 *
 * @author Christoph Strobl
 * @author John Dorman
 * @author Rosty Kerei
 * @author Luke Corpe
 * @author Andrey Paramonov
 * @author Philipp Jardas
 * @author Francisco Spaeth
 * @author Joachim Uhrla
 * @author Petar Tahchiev
 * @author Juan Manuel de Blas
 */
public class DefaultQueryParser extends QueryParserBase<SolrDataQuery> {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultQueryParser.class);

    /**
     * Create a new {@link DefaultQueryParser} using the provided {@link MappingContext} to map {@link Field fields} to
     * domain domain type {@link org.springframework.data.mapping.PersistentProperty properties}.
     *
     * @param mappingContext can be {@literal null}.
     * @since 4.0
     */
    public DefaultQueryParser(@Nullable MappingContext mappingContext) {
        super(mappingContext);
    }

    /**
     * Convert given Query into a SolrQuery executable via {@link org.apache.solr.client.solrj.SolrClient}
     *
     * @param query the source query to turn into a {@link SolrQuery}.
     * @param domainType can be {@literal null}.
     * @return
     */
    @Override
    public final SolrQuery doConstructSolrQuery(SolrDataQuery query, @Nullable Class<?> domainType) {

        Assert.notNull(query, "Cannot construct solrQuery from null value.");
        Assert.notNull(query.getCriteria(), "Query has to have a criteria.");

        SolrQuery solrQuery = new SolrQuery();
        solrQuery.setParam(CommonParams.Q, getQueryString(query, domainType));

        if (query instanceof Query) {
            processQueryOptions(solrQuery, (Query) query, domainType);
        }

        if (query instanceof FacetQuery) {
            processFacetOptions(solrQuery, (FacetQuery) query, domainType);
        }

        if (query instanceof HighlightQuery) {
            processHighlightOptions(solrQuery, (HighlightQuery) query, domainType);
        }

        return solrQuery;
    }

    private void processQueryOptions(SolrQuery solrQuery, Query query, @Nullable Class<?> domainType) {

        appendPagination(solrQuery, query.getOffset(), query.getRows());
        appendProjectionOnFields(solrQuery, query.getProjectionOnFields(), domainType);
        appendFilterQuery(solrQuery, query.getFilterQueries(), domainType);
        appendSort(solrQuery, query.getSort(), domainType);
        appendDefaultOperator(solrQuery, query.getDefaultOperator());
        appendTimeAllowed(solrQuery, query.getTimeAllowed());
        appendDefType(solrQuery, query.getDefType());
        appendRequestHandler(solrQuery, query.getRequestHandler());

        processGroupOptions(solrQuery, query, domainType);
        processStatsOptions(solrQuery, query, domainType);
        processSpellcheckOptions(solrQuery, query, domainType);

        appendGeoParametersIfRequired(solrQuery, query, domainType);

        LOGGER.debug("Constructed SolrQuery:\r\n {}", solrQuery);
    }

    private void processFacetOptions(SolrQuery solrQuery, FacetQuery query, @Nullable Class<?> domainType) {

        if (enableFaceting(solrQuery, query)) {
            appendFacetingOnFields(solrQuery, query, domainType);
            appendFacetingQueries(solrQuery, query, domainType);
            appendFacetingOnPivot(solrQuery, query, domainType);
            appendRangeFacetingOnFields(solrQuery, query, domainType);
        }
    }

    private void setObjectNameOnGroupQuery(Query query, Object object, String name) {

        if (query instanceof NamedObjectsQuery) {
            ((NamedObjectsQuery) query).setName(object, name);
        }
    }

    private void processStatsOptions(SolrQuery solrQuery, Query query, @Nullable Class<?> domainType) {

        StatsOptions statsOptions = query.getStatsOptions();

        if (statsOptions == null || (CollectionUtils.isEmpty(statsOptions.getFields())
                && CollectionUtils.isEmpty(statsOptions.getFacets())
                && CollectionUtils.isEmpty(statsOptions.getSelectiveFacets()))) {
            return;
        }

        solrQuery.set(StatsParams.STATS, true);

        for (Field field : statsOptions.getFields()) {

            String mappedFieldName = getMappedFieldName(field, domainType);
            solrQuery.add(StatsParams.STATS_FIELD, mappedFieldName);

            String selectiveCalcDistinctParam = CommonParams.FIELD + "." + mappedFieldName + "."
                    + StatsParams.STATS_CALC_DISTINCT;
            Boolean selectiveCountDistincts = statsOptions.isSelectiveCalcDistincts(field);

            if (selectiveCountDistincts != null) {
                solrQuery.add(selectiveCalcDistinctParam, String.valueOf(selectiveCountDistincts.booleanValue()));
            }
        }

        for (Field field : statsOptions.getFacets()) {
            solrQuery.add(StatsParams.STATS_FACET, getMappedFieldName(field, domainType));
        }

        for (Entry<Field, Collection<Field>> entry : statsOptions.getSelectiveFacets().entrySet()) {

            Field field = entry.getKey();
            String prefix = CommonParams.FIELD + "." + getMappedFieldName(field, domainType) + ".";

            String paramName = prefix + StatsParams.STATS_FACET;
            for (Field facetField : entry.getValue()) {
                solrQuery.add(paramName, getMappedFieldName(facetField, domainType));
            }
        }
    }

    private void processGroupOptions(SolrQuery solrQuery, Query query, Class<?> domainType) {

        GroupOptions groupOptions = query.getGroupOptions();

        if (groupOptions == null || (CollectionUtils.isEmpty(groupOptions.getGroupByFields())
                && CollectionUtils.isEmpty(groupOptions.getGroupByFunctions())
                && CollectionUtils.isEmpty(groupOptions.getGroupByQueries()))) {
            return;
        }

        solrQuery.set(GroupParams.GROUP, true);
        solrQuery.set(GroupParams.GROUP_MAIN, groupOptions.isGroupMain());
        solrQuery.set(GroupParams.GROUP_FORMAT, "grouped");

        if (!CollectionUtils.isEmpty(groupOptions.getGroupByFields())) {
            for (Field field : groupOptions.getGroupByFields()) {
                solrQuery.add(GroupParams.GROUP_FIELD, getMappedFieldName(field, domainType));
            }
        }

        if (!CollectionUtils.isEmpty(groupOptions.getGroupByFunctions())) {
            for (Function function : groupOptions.getGroupByFunctions()) {
                String functionFragment = createFunctionFragment(function, 0, domainType, Target.QUERY);
                setObjectNameOnGroupQuery(query, function, functionFragment);
                solrQuery.add(GroupParams.GROUP_FUNC, functionFragment);
            }
        }

        if (!CollectionUtils.isEmpty(groupOptions.getGroupByQueries())) {
            for (Query groupQuery : groupOptions.getGroupByQueries()) {
                String queryFragment = getQueryString(groupQuery, domainType);
                setObjectNameOnGroupQuery(query, groupQuery, queryFragment);
                solrQuery.add(GroupParams.GROUP_QUERY, queryFragment);
            }
        }

        if (groupOptions.getSort() != null) {

            for (Order order : groupOptions.getSort()) {
                solrQuery.add(GroupParams.GROUP_SORT, getMappedFieldName(order.getProperty().trim(), domainType)
                        + " " + (order.isAscending() ? ORDER.asc : ORDER.desc));
            }
        }

        if (groupOptions.getCachePercent() > 0) {
            solrQuery.add(GroupParams.GROUP_CACHE_PERCENTAGE, String.valueOf(groupOptions.getCachePercent()));
        }

        if (groupOptions.getLimit() != null) {
            solrQuery.set(GroupParams.GROUP_LIMIT, groupOptions.getLimit());
        }

        if (groupOptions.getOffset() != null && groupOptions.getOffset() >= 0) {
            solrQuery.set(GroupParams.GROUP_OFFSET, groupOptions.getOffset());
        }

        solrQuery.set(GroupParams.GROUP_TOTAL_COUNT, groupOptions.isTotalCount());
        solrQuery.set(GroupParams.GROUP_FACET, groupOptions.isGroupFacets());
        solrQuery.set(GroupParams.GROUP_TRUNCATE, groupOptions.isTruncateFacets());
    }

    private void processSpellcheckOptions(SolrQuery solrQuery, Query query, @Nullable Class<?> domainType) {

        if (query.getSpellcheckOptions() == null) {
            return;
        }

        SpellcheckOptions options = query.getSpellcheckOptions();

        if (options.getQuery() != null && options.getQuery().getCriteria() != null) {
            solrQuery.set(SpellingParams.SPELLCHECK_Q,
                    createQueryStringFromCriteria(options.getQuery().getCriteria(), domainType));
        }

        ModifiableSolrParams params = new ModifiableSolrParams();
        params.add("spellcheck", "on");
        for (Entry<String, Object> entry : options.getParams().entrySet()) {

            if (entry.getValue() instanceof Iterable<?>) {
                for (Object o : ((Iterable<?>) entry.getValue())) {
                    params.add(entry.getKey(), o.toString());
                }
            } else if (ObjectUtils.isArray(entry.getValue())) {
                for (Object o : ObjectUtils.toObjectArray(entry.getValue())) {
                    params.add(entry.getKey(), o.toString());
                }
            } else {
                params.add(entry.getKey(), entry.getValue().toString());
            }
        }
        solrQuery.add(params);
    }

    /**
     * Append highlighting parameters to {@link SolrQuery}
     *
     * @param solrQuery the target {@link SolrQuery}
     * @param query the source query.
     * @param domainType used for mapping fields to properties. Can be {@literal null}.
     */
    protected void processHighlightOptions(SolrQuery solrQuery, HighlightQuery query,
            @Nullable Class<?> domainType) {

        if (query.hasHighlightOptions()) {

            HighlightOptions highlightOptions = query.getHighlightOptions();
            solrQuery.setHighlight(true);

            if (!highlightOptions.hasFields()) {
                solrQuery.addHighlightField(HighlightOptions.ALL_FIELDS.getName());
            } else {
                for (Field field : highlightOptions.getFields()) {
                    solrQuery.addHighlightField(getMappedFieldName(field, domainType));
                }
                for (FieldWithHighlightParameters fieldWithHighlightParameters : highlightOptions
                        .getFieldsWithHighlightParameters()) {
                    addPerFieldHighlightParameters(solrQuery, fieldWithHighlightParameters, domainType);
                }
            }

            for (HighlightParameter option : highlightOptions.getHighlightParameters()) {
                addOptionToSolrQuery(solrQuery, option);
            }

            if (highlightOptions.hasQuery()) {
                solrQuery.add(HighlightParams.Q, getQueryString(highlightOptions.getQuery(), domainType));
            }
        }
    }

    private void addOptionToSolrQuery(SolrQuery solrQuery, QueryParameter option) {

        if (option != null && StringUtils.isNotBlank(option.getName())) {
            solrQuery.add(option.getName(), conversionService.convert(option.getValue(), String.class));
        }
    }

    private void addFieldSpecificParameterToSolrQuery(SolrQuery solrQuery, Field field, QueryParameter option,
            @Nullable Class<?> domainType) {

        if (option != null && field != null && StringUtils.isNotBlank(option.getName())) {
            if (option.getValue() == null) {
                solrQuery.add(createPerFieldOverrideParameterName(field, option.getName(), domainType),
                        (String) null);
            } else {
                String value = option.getValue().toString();
                if (conversionService.canConvert(option.getValue().getClass(), String.class)) {
                    value = conversionService.convert(option.getValue(), String.class);
                }
                solrQuery.add(createPerFieldOverrideParameterName(field, option.getName(), domainType), value);
            }
        }
    }

    private void addPerFieldHighlightParameters(SolrQuery solrQuery, FieldWithHighlightParameters field,
            @Nullable Class<?> domainType) {

        for (HighlightParameter option : field) {
            addFieldSpecificParameterToSolrQuery(solrQuery, field, option, domainType);
        }
    }

    /**
     * @param field the source field.
     * @param parameterName the parameter name to append
     * @param domainType used for mapping fields to properties. Can be {@literal null}.
     * @return
     */
    protected String createPerFieldOverrideParameterName(Field field, String parameterName,
            @Nullable Class<?> domainType) {
        return "f." + getMappedFieldName(field, domainType) + "." + parameterName;
    }

    private boolean enableFaceting(SolrQuery solrQuery, FacetQuery query) {

        FacetOptions facetOptions = query.getFacetOptions();
        if (facetOptions == null || !facetOptions.hasFacets()) {
            return false;
        }
        solrQuery.setFacet(true);
        solrQuery.setFacetMinCount(facetOptions.getFacetMinCount());
        solrQuery.setFacetLimit(facetOptions.getPageable().getPageSize());
        if (facetOptions.getPageable().getPageNumber() > 0) {
            long offset = Math.max(0, facetOptions.getPageable().getOffset());
            solrQuery.set(FacetParams.FACET_OFFSET, "" + offset);
        }
        if (FacetOptions.FacetSort.INDEX.equals(facetOptions.getFacetSort())) {
            solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX);
        }
        return true;
    }

    private void appendFacetingOnFields(SolrQuery solrQuery, FacetQuery query, @Nullable Class<?> domainType) {

        FacetOptions facetOptions = query.getFacetOptions();
        solrQuery.addFacetField(convertFieldListToStringArray(facetOptions.getFacetOnFields(), domainType));

        if (facetOptions.hasFacetPrefix()) {
            solrQuery.setFacetPrefix(facetOptions.getFacetPrefix());
        }
        for (FieldWithFacetParameters parametrizedField : facetOptions.getFieldsWithParameters()) {
            addPerFieldFacetParameters(solrQuery, parametrizedField, domainType);
            if (parametrizedField.getSort() != null
                    && FacetOptions.FacetSort.INDEX.equals(parametrizedField.getSort())) {
                addFieldSpecificParameterToSolrQuery(solrQuery, parametrizedField,
                        new FacetParameter(FacetParams.FACET_SORT, FacetParams.FACET_SORT_INDEX), domainType);
            }

        }
    }

    private void addPerFieldFacetParameters(SolrQuery solrQuery, FieldWithFacetParameters field,
            @Nullable Class<?> domainType) {

        for (FacetParameter parameter : field) {
            addFieldSpecificParameterToSolrQuery(solrQuery, field, parameter, domainType);
        }
    }

    private void appendRangeFacetingOnFields(SolrQuery solrQuery, FacetQuery query, @Nullable Class<?> domainType) {

        FacetOptions facetRangeOptions = query.getFacetOptions();

        if (facetRangeOptions == null) {
            return;
        }

        for (FieldWithRangeParameters<?, ?, ?> rangeField : facetRangeOptions.getFieldsWithRangeParameters()) {

            if (rangeField instanceof FieldWithDateRangeParameters) {
                appendFieldFacetingByDateRange(solrQuery, (FieldWithDateRangeParameters) rangeField, domainType);
            } else if (rangeField instanceof FieldWithNumericRangeParameters) {
                appendFieldFacetingByNumberRange(solrQuery, (FieldWithNumericRangeParameters) rangeField,
                        domainType);
            }

            if (rangeField.getHardEnd() != null && rangeField.getHardEnd()) {
                FacetParameter param = new FacetParameter(FacetParams.FACET_RANGE_HARD_END, true);
                addFieldSpecificParameterToSolrQuery(solrQuery, rangeField, param, domainType);
            }

            if (rangeField.getOther() != null) {
                FacetParameter param = new FacetParameter(FacetParams.FACET_RANGE_OTHER, rangeField.getOther());
                addFieldSpecificParameterToSolrQuery(solrQuery, rangeField, param, domainType);
            }

            if (rangeField.getInclude() != null) {
                FacetParameter param = new FacetParameter(FacetParams.FACET_RANGE_INCLUDE, rangeField.getInclude());
                addFieldSpecificParameterToSolrQuery(solrQuery, rangeField, param, domainType);
            }

        }
    }

    private void appendFieldFacetingByNumberRange(SolrQuery solrQuery, FieldWithNumericRangeParameters field,
            @Nullable Class<?> domainType) {

        solrQuery.addNumericRangeFacet( //
                getMappedFieldName(field, domainType), //
                field.getStart(), //
                field.getEnd(), //
                field.getGap());
    }

    private void appendFieldFacetingByDateRange(SolrQuery solrQuery, FieldWithDateRangeParameters field,
            @Nullable Class<?> domainType) {

        solrQuery.addDateRangeFacet( //
                getMappedFieldName(field, domainType), //
                field.getStart(), //
                field.getEnd(), //
                field.getGap());
    }

    private void appendFacetingQueries(SolrQuery solrQuery, FacetQuery query, @Nullable Class<?> domainType) {

        FacetOptions facetOptions = query.getFacetOptions();
        for (SolrDataQuery fq : facetOptions.getFacetQueries()) {
            String facetQueryString = getQueryString(fq, domainType);
            if (StringUtils.isNotBlank(facetQueryString)) {
                solrQuery.addFacetQuery(facetQueryString);
            }
        }
    }

    private void appendFacetingOnPivot(SolrQuery solrQuery, FacetQuery query, @Nullable Class<?> domainType) {

        FacetOptions facetOptions = query.getFacetOptions();
        String[] pivotFields = convertFieldListToStringArray(facetOptions.getFacetOnPivots(), domainType);
        solrQuery.addFacetPivotField(pivotFields);
    }

    /**
     * Set filter filter queries for {@link SolrQuery}
     *
     * @param solrQuery
     * @param filterQueries
     * @param domainType used for mapping fields to properties. Can be {@literal null}.
     */
    protected void appendFilterQuery(SolrQuery solrQuery, List<FilterQuery> filterQueries,
            @Nullable Class<?> domainType) {

        if (CollectionUtils.isEmpty(filterQueries)) {
            return;
        }

        List<String> filterQueryStrings = getFilterQueryStrings(filterQueries, domainType);

        if (!filterQueryStrings.isEmpty()) {
            solrQuery.setFilterQueries(convertStringListToArray(filterQueryStrings));
        }
    }

    /**
     * Append sorting parameters to {@link SolrQuery}
     *
     * @param solrQuery
     * @param sort
     */
    protected void appendSort(SolrQuery solrQuery, @Nullable Sort sort, @Nullable Class<?> domainType) {

        if (sort == null) {
            return;
        }

        for (Order order : sort) {
            solrQuery.addSort(getMappedFieldName(order.getProperty(), domainType),
                    order.isAscending() ? ORDER.asc : ORDER.desc);
        }
    }

    private String[] convertFieldListToStringArray(List<? extends Field> fields, @Nullable Class<?> domainType) {

        String[] strResult = new String[fields.size()];
        for (int i = 0; i < fields.size(); i++) {

            Field field = fields.get(i);

            if (field instanceof PivotField) {

                if (field.getName().contains(",")) {

                    String[] args = field.getName().split(",");
                    String[] mapped = new String[args.length];

                    for (int j = 0; j < args.length; j++) {
                        mapped[j] = getMappedFieldName(args[j], domainType);
                    }

                    strResult[i] = org.springframework.util.StringUtils.arrayToCommaDelimitedString(mapped);
                } else {
                    strResult[i] = field.getName();
                }

            } else {
                strResult[i] = getMappedFieldName(field, domainType);
            }
        }
        return strResult;
    }

    private String[] convertStringListToArray(List<String> listOfString) {

        String[] strResult = new String[listOfString.size()];
        listOfString.toArray(strResult);
        return strResult;
    }

    private List<String> getFilterQueryStrings(List<FilterQuery> filterQueries, @Nullable Class<?> domainType) {
        List<String> filterQueryStrings = new ArrayList<>(filterQueries.size());

        for (FilterQuery filterQuery : filterQueries) {
            String filterQueryString = getQueryString(filterQuery, domainType);
            if (StringUtils.isNotBlank(filterQueryString)) {
                filterQueryStrings.add(filterQueryString);
            }
        }
        return filterQueryStrings;
    }
}