/* * Copyright 2012 - 2016 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.repository.query; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.solr.core.query.SpellcheckOptions; import org.springframework.data.solr.repository.Facet; import org.springframework.data.solr.repository.Highlight; import org.springframework.data.solr.repository.Pivot; import org.springframework.data.solr.repository.Query; import org.springframework.data.solr.repository.SelectiveStats; import org.springframework.data.solr.repository.Spellcheck; import org.springframework.data.solr.repository.Stats; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Solr specific implementation of {@link QueryMethod} taking care of {@link Query} * * @author Christoph Strobl * @author Luke Corpe * @author Andrey Paramonov * @author Francisco Spaeth */ public class SolrQueryMethod extends QueryMethod { private final Method method; public SolrQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, SolrEntityInformationCreator solrInformationCreator) { super(method, metadata, factory); this.method = method; } /** * @return true if {@link Query#value()} is not blank */ public boolean hasAnnotatedQuery() { return getAnnotatedQuery() != null; } /** * @return true if {@link Query} is not blank */ public boolean hasQueryAnnotation() { return this.method.getAnnotation(Query.class) != null; } String getAnnotatedQuery() { return getAnnotationValueAsStringOrNullIfBlank(getQueryAnnotation(), "value"); } /** * @return true if {@link Query#name()} is not blank */ public boolean hasAnnotatedNamedQueryName() { return getAnnotatedNamedQueryName() != null; } String getAnnotatedNamedQueryName() { return getAnnotationValueAsStringOrNullIfBlank(getQueryAnnotation(), "name"); } private Query getQueryAnnotation() { return this.method.getAnnotation(Query.class); } TypeInformation<?> getReturnType() { return ClassTypeInformation.fromReturnTypeOf(method); } /** * @return true if {@link Query#fields()} is not empty */ public boolean hasProjectionFields() { return hasQueryAnnotation() && !CollectionUtils.isEmpty(getProjectionFields()); } /** * @return empty collection if {@link Query#fields()} is empty */ public List<String> getProjectionFields() { return getAnnotationValuesAsStringList(getQueryAnnotation(), "fields"); } /** * @return null if {@link Query#timeAllowed()} is null or negative */ public Integer getTimeAllowed() { if (hasQueryAnnotation()) { return getAnnotationValueAsIntOrNullIfNegative(getQueryAnnotation(), "timeAllowed"); } return null; } /** * @return true if {@link #hasFacetFields()} or {@link #hasFacetQueries()} */ public boolean isFacetQuery() { return hasFacetFields() || hasFacetQueries() || hasPivotFields(); } /** * @return true if {@link Facet#fields()} is not empty */ public boolean hasFacetFields() { return hasFacetAnnotation() && !CollectionUtils.isEmpty(getFacetFields()); } /** * @return true if is not empty */ public boolean hasPivotFields() { return hasFacetAnnotation() && !CollectionUtils.isEmpty(getPivotFields()); } private boolean hasFacetAnnotation() { return getFacetAnnotation() != null; } /** * @return empty collection if {@link Facet#fields()} is empty */ public List<String> getFacetFields() { return getAnnotationValuesAsStringList(getFacetAnnotation(), "fields"); } /** * @return empty collection if {@link Facet#queries()} is empty */ public List<String> getFacetQueries() { return getAnnotationValuesAsStringList(getFacetAnnotation(), "queries"); } public List<String[]> getPivotFields() { List<Pivot> pivotFields = getAnnotationValuesList(getFacetAnnotation(), "pivots", Pivot.class); ArrayList<String[]> result = new ArrayList<>(); for (Pivot pivot : pivotFields) { result.add(pivot.value()); } return result; } /** * @return true if {@link Facet#queries()} is not empty */ public boolean hasFacetQueries() { return hasFacetAnnotation() && !CollectionUtils.isEmpty(getFacetQueries()); } private Facet getFacetAnnotation() { return this.method.getAnnotation(Facet.class); } /** * @return value of {@link Facet#limit()} */ public Integer getFacetLimit() { return (Integer) AnnotationUtils.getValue(getFacetAnnotation(), "limit"); } /** * @return value of {@link Facet#minCount()} */ public Integer getFacetMinCount() { return (Integer) AnnotationUtils.getValue(getFacetAnnotation(), "minCount"); } /** * @return value of {@link Facet#prefix()} */ public String getFacetPrefix() { return getAnnotationValueAsStringOrNullIfBlank(getFacetAnnotation(), "prefix"); } /** * @return the {@link Stats} annotation, null if there is none */ private Stats getStatsAnnotation() { return this.method.getAnnotation(Stats.class); } /** * @return if something was configured within {@link Stats} * @since 1.4 */ public boolean hasStatsDefinition() { return (// !(getStatsAnnotation() == null) && (// !getFieldStats().isEmpty() || // !getStatsFacets().isEmpty() || // !getStatsSelectiveFacets().isEmpty() || // !getStatsSelectiveCountDistinctFields().isEmpty())// ); } /** * @return true if stats is distinct * @since 1.4 */ public boolean isFieldStatsCountDistinctEnable() { Stats stats = getStatsAnnotation(); return stats != null && stats.distinct(); } /** * @return value of {@link Stats#value()} * @since 1.4 */ public List<String> getFieldStats() { return getAnnotationValuesAsStringList(getStatsAnnotation(), "value"); } /** * @return value of {@link Stats#facets()} * @since 1.4 */ public List<String> getStatsFacets() { return getAnnotationValuesAsStringList(getStatsAnnotation(), "facets"); } /** * @return value of facets used in {@link Stats#selective()} * @since 1.4 */ public Map<String, String[]> getStatsSelectiveFacets() { List<SelectiveStats> selective = getAnnotationValuesList(getStatsAnnotation(), "selective", SelectiveStats.class); Map<String, String[]> result = new LinkedHashMap<>(); for (SelectiveStats selectiveFacet : selective) { result.put(selectiveFacet.field(), selectiveFacet.facets()); } return result; } /** * @return value of facets used in {@link Stats#selective()} * @since 1.4 */ public Collection<String> getStatsSelectiveCountDistinctFields() { List<SelectiveStats> selective = getAnnotationValuesList(getStatsAnnotation(), "selective", SelectiveStats.class); Collection<String> result = new LinkedHashSet<>(); for (SelectiveStats selectiveFacet : selective) { if (selectiveFacet.distinct()) { result.add(selectiveFacet.field()); } } return result; } /** * @return true if {@link Query#filters()} is not empty */ public boolean hasFilterQuery() { return hasQueryAnnotation() && !CollectionUtils.isEmpty(getFilterQueries()); } /** * @return value of {@link Query#delete()} * @since 1.2 */ public boolean isDeleteQuery() { return hasQueryAnnotation() && (Boolean) AnnotationUtils.getValue(getQueryAnnotation(), "delete"); } private Annotation getHighlightAnnotation() { return this.method.getAnnotation(Highlight.class); } private boolean hasHighlightAnnotation() { return this.getHighlightAnnotation() != null; } /** * @return if {@link Highlight} is present */ public boolean isHighlightQuery() { return this.hasHighlightAnnotation(); } /** * @return empty collection if {@link Highlight#fields()} is empty */ public List<String> getHighlightFieldNames() { if (hasHighlightAnnotation()) { return this.getAnnotationValuesAsStringList(getHighlightAnnotation(), "fields"); } return Collections.emptyList(); } /** * @return null if {@link Highlight#query()} is blank */ public String getHighlightQuery() { if (hasHighlightAnnotation()) { return getAnnotationValueAsStringOrNullIfBlank(getHighlightAnnotation(), "query"); } return null; } /** * @return value of {@link Highlight#snipplets()} or null if negative */ public Integer getHighlighSnipplets() { if (hasHighlightAnnotation()) { return getAnnotationValueAsIntOrNullIfNegative(getHighlightAnnotation(), "snipplets"); } return null; } /** * @return value of {@link Highlight#fragsize()} or null if negative */ public Integer getHighlightFragsize() { if (hasHighlightAnnotation()) { return getAnnotationValueAsIntOrNullIfNegative(getHighlightAnnotation(), "fragsize"); } return null; } /** * @return value of {@link Highlight#formatter()} or null if blank */ public String getHighlightFormatter() { if (hasHighlightAnnotation()) { return getAnnotationValueAsStringOrNullIfBlank(getHighlightAnnotation(), "formatter"); } return null; } /** * @return value of {@link Highlight#prefix()} or null if blank */ public String getHighlightPrefix() { if (hasHighlightAnnotation()) { return getAnnotationValueAsStringOrNullIfBlank(getHighlightAnnotation(), "prefix"); } return null; } /** * @return value of {@link Highlight#postfix()} or null if blank */ public String getHighlightPostfix() { if (hasHighlightAnnotation()) { return getAnnotationValueAsStringOrNullIfBlank(getHighlightAnnotation(), "postfix"); } return null; } /** * @return true if {@link Highlight#fields()} is not empty */ public boolean hasHighlightFields() { return !getHighlightFieldNames().isEmpty(); } List<String> getFilterQueries() { return getAnnotationValuesAsStringList(getQueryAnnotation(), "filters"); } /** * @return value of {@link Query#defaultOperator()} or * {@link org.springframework.data.solr.core.query.Query.Operator#NONE} if not set */ public org.springframework.data.solr.core.query.Query.Operator getDefaultOperator() { if (hasQueryAnnotation()) { return getQueryAnnotation().defaultOperator(); } return org.springframework.data.solr.core.query.Query.Operator.NONE; } /** * @return null if {@link Query#defType()} not set */ public String getDefType() { if (hasQueryAnnotation()) { return getQueryAnnotation().defType(); } return null; } /** * @return null if {@link Query#requestHandler()} not set */ public String getRequestHandler() { if (hasQueryAnnotation()) { return getQueryAnnotation().requestHandler(); } return null; } /** * @return * @since 2.1 */ public Spellcheck getSpellcheckAnnotation() { return AnnotatedElementUtils.findMergedAnnotation(this.method, Spellcheck.class); } /** * @return * @since 2.1 */ public boolean hasSpellcheck() { return getSpellcheckAnnotation() != null; } /** * @return * @since 2.1 */ public SpellcheckOptions getSpellcheckOptions() { Spellcheck spellcheck = getSpellcheckAnnotation(); if (spellcheck == null) { return null; } SpellcheckOptions sc = SpellcheckOptions.spellcheck(); if (spellcheck.accuracy() >= 0F) { sc = sc.accuracy(spellcheck.accuracy()); } if (spellcheck.buildDictionary()) { sc = sc.buildDictionary(); } if (spellcheck.collate()) { sc = sc.collate(); } if (spellcheck.collateExtendedResults()) { sc = sc.collateExtendedResults(); } if (spellcheck.onlyMorePopular()) { sc = sc.onlyMorePopular(); } if (spellcheck.alternativeTermCount() >= 0) { sc = sc.alternativeTermCount(spellcheck.alternativeTermCount()); } if (spellcheck.count() >= 0) { sc = sc.count(spellcheck.count()); } if (!ObjectUtils.isEmpty(spellcheck.dictionaries())) { sc = sc.dictionaries(spellcheck.dictionaries()); } if (spellcheck.maxCollationEvaluations() >= 0) { sc = sc.maxCollationEvaluations(spellcheck.maxCollationEvaluations()); } if (spellcheck.maxCollations() >= 0) { sc = sc.maxCollations(spellcheck.maxCollations()); } if (spellcheck.maxCollationsTries() >= 0) { sc = sc.maxCollationTries(spellcheck.maxCollationsTries()); } if (spellcheck.maxResultsForSuggest() >= 0) { sc = sc.maxResultsForSuggest(spellcheck.maxResultsForSuggest()); } if (spellcheck.maxCollationCollectDocs() >= 0) { sc = sc.maxCollationCollectDocs(spellcheck.maxCollationCollectDocs()); } if (spellcheck.extendedResults()) { sc = sc.extendedResults(); } return sc; } private String getAnnotationValueAsStringOrNullIfBlank(Annotation annotation, String attributeName) { String value = (String) AnnotationUtils.getValue(annotation, attributeName); return StringUtils.hasText(value) ? value : null; } private Integer getAnnotationValueAsIntOrNullIfNegative(Annotation annotation, String attributeName) { Integer timeAllowed = (Integer) AnnotationUtils.getValue(annotation, attributeName); if (timeAllowed != null && timeAllowed > 0) { return timeAllowed; } return null; } @SuppressWarnings("unchecked") private List<String> getAnnotationValuesAsStringList(Annotation annotation, String attribute) { String[] values = (String[]) AnnotationUtils.getValue(annotation, attribute); if (values.length > 1 || (values.length == 1 && StringUtils.hasText(values[0]))) { return CollectionUtils.arrayToList(values); } return Collections.emptyList(); } @SuppressWarnings("unchecked") private <T> List<T> getAnnotationValuesList(Annotation annotation, String attribute, Class<T> clazz) { T[] values = (T[]) AnnotationUtils.getValue(annotation, attribute); return CollectionUtils.arrayToList(values); } @Override public String getNamedQueryName() { if (!hasAnnotatedNamedQueryName()) { return super.getNamedQueryName(); } return getAnnotatedNamedQueryName(); } @Override protected SolrParameters createParameters(Method method) { return new SolrParameters(method); } @Override public SolrParameters getParameters() { return (SolrParameters) super.getParameters(); } }