mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 23:32:12 +00:00
Support highlight query in @HighlightParameters annotation.
Original Pull Request #2802
This commit is contained in:
parent
d0ed80dfde
commit
96b38652ab
@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
@ -59,6 +60,8 @@ public @interface HighlightParameters {
|
|||||||
|
|
||||||
int numberOfFragments() default -1;
|
int numberOfFragments() default -1;
|
||||||
|
|
||||||
|
Query highlightQuery() default @Query;
|
||||||
|
|
||||||
String order() default "";
|
String order() default "";
|
||||||
|
|
||||||
int phraseLimit() default -1;
|
int phraseLimit() default -1;
|
||||||
|
@ -15,14 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.query.highlight;
|
package org.springframework.data.elasticsearch.core.query.highlight;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
public class Highlight {
|
public class Highlight {
|
||||||
@ -57,42 +56,4 @@ public class Highlight {
|
|||||||
public List<HighlightField> getFields() {
|
public List<HighlightField> getFields() {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link Highlight} from an Annotation instance.
|
|
||||||
*
|
|
||||||
* @param highlight must not be {@literal null}
|
|
||||||
* @return highlight definition
|
|
||||||
*/
|
|
||||||
public static Highlight of(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
|
|
||||||
|
|
||||||
Assert.notNull(highlight, "highlight must not be null");
|
|
||||||
|
|
||||||
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
|
|
||||||
HighlightParameters highlightParameters = HighlightParameters.builder() //
|
|
||||||
.withBoundaryChars(parameters.boundaryChars()) //
|
|
||||||
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
|
|
||||||
.withBoundaryScanner(parameters.boundaryScanner()) //
|
|
||||||
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
|
|
||||||
.withEncoder(parameters.encoder()) //
|
|
||||||
.withForceSource(parameters.forceSource()) //
|
|
||||||
.withFragmenter(parameters.fragmenter()) //
|
|
||||||
.withFragmentSize(parameters.fragmentSize()) //
|
|
||||||
.withNoMatchSize(parameters.noMatchSize()) //
|
|
||||||
.withNumberOfFragments(parameters.numberOfFragments()) //
|
|
||||||
.withOrder(parameters.order()) //
|
|
||||||
.withPhraseLimit(parameters.phraseLimit()) //
|
|
||||||
.withPreTags(parameters.preTags()) //
|
|
||||||
.withPostTags(parameters.postTags()) //
|
|
||||||
.withRequireFieldMatch(parameters.requireFieldMatch()) //
|
|
||||||
.withTagsSchema(parameters.tagsSchema()) //
|
|
||||||
.withType(parameters.type()) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
|
|
||||||
.map(HighlightField::of) //
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return new Highlight(highlightParameters, highlightFields);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ public abstract class HighlightCommonParameters {
|
|||||||
return (SELF) this;
|
return (SELF) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF withHighlightQuery(Query highlightQuery) {
|
public SELF withHighlightQuery(@Nullable Query highlightQuery) {
|
||||||
this.highlightQuery = highlightQuery;
|
this.highlightQuery = highlightQuery;
|
||||||
return (SELF) this;
|
return (SELF) this;
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.repository.query;
|
package org.springframework.data.elasticsearch.repository.query;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||||
@ -50,12 +42,19 @@ import org.springframework.data.repository.query.Parameters;
|
|||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||||
import org.springframework.data.util.Lazy;
|
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ElasticsearchQueryMethod
|
* ElasticsearchQueryMethod
|
||||||
*
|
*
|
||||||
@ -66,6 +65,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @author Alexander Torres
|
* @author Alexander Torres
|
||||||
|
* @author Haibo Liu
|
||||||
*/
|
*/
|
||||||
public class ElasticsearchQueryMethod extends QueryMethod {
|
public class ElasticsearchQueryMethod extends QueryMethod {
|
||||||
|
|
||||||
@ -81,8 +81,6 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
@Nullable private ElasticsearchEntityMetadata<?> metadata;
|
@Nullable private ElasticsearchEntityMetadata<?> metadata;
|
||||||
@Nullable private final Query queryAnnotation;
|
@Nullable private final Query queryAnnotation;
|
||||||
@Nullable private final Highlight highlightAnnotation;
|
@Nullable private final Highlight highlightAnnotation;
|
||||||
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
|
|
||||||
|
|
||||||
@Nullable private final SourceFilters sourceFilters;
|
@Nullable private final SourceFilters sourceFilters;
|
||||||
|
|
||||||
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
||||||
@ -143,19 +141,13 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
* @throws IllegalArgumentException if no {@link Highlight} annotation is present on the method
|
* @throws IllegalArgumentException if no {@link Highlight} annotation is present on the method
|
||||||
* @see #hasAnnotatedHighlight()
|
* @see #hasAnnotatedHighlight()
|
||||||
*/
|
*/
|
||||||
public HighlightQuery getAnnotatedHighlightQuery() {
|
public HighlightQuery getAnnotatedHighlightQuery(HighlightConverter highlightConverter) {
|
||||||
|
|
||||||
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
|
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
|
||||||
|
|
||||||
return highlightQueryLazy.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private HighlightQuery createAnnotatedHighlightQuery() {
|
|
||||||
|
|
||||||
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
|
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
|
||||||
|
|
||||||
return new HighlightQuery(
|
return new HighlightQuery(
|
||||||
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
|
highlightConverter.convert(highlightAnnotation),
|
||||||
getDomainClass());
|
getDomainClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +370,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
ElasticsearchConverter elasticsearchConverter) {
|
ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
|
||||||
if (hasAnnotatedHighlight()) {
|
if (hasAnnotatedHighlight()) {
|
||||||
query.setHighlightQuery(getAnnotatedHighlightQuery());
|
query.setHighlightQuery(getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor, elasticsearchConverter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
|
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013-2023 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.elasticsearch.repository.query;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
|
||||||
|
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert {@link org.springframework.data.elasticsearch.annotations.Highlight} to {@link Highlight}.
|
||||||
|
*
|
||||||
|
* @author Haibo Liu
|
||||||
|
*/
|
||||||
|
public class HighlightConverter {
|
||||||
|
|
||||||
|
private final ElasticsearchParametersParameterAccessor parameterAccessor;
|
||||||
|
private final ElasticsearchConverter elasticsearchConverter;
|
||||||
|
|
||||||
|
HighlightConverter(ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||||
|
ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
this.parameterAccessor = parameterAccessor;
|
||||||
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Highlight} from an Annotation instance.
|
||||||
|
*
|
||||||
|
* @param highlight must not be {@literal null}
|
||||||
|
* @return highlight definition
|
||||||
|
*/
|
||||||
|
Highlight convert(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
|
||||||
|
|
||||||
|
Assert.notNull(highlight, "highlight must not be null");
|
||||||
|
|
||||||
|
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
|
||||||
|
|
||||||
|
// replace placeholders in highlight query with actual parameters
|
||||||
|
Query highlightQuery = null;
|
||||||
|
if (!parameters.highlightQuery().value().isEmpty()) {
|
||||||
|
String rawString = parameters.highlightQuery().value();
|
||||||
|
String queryString = new StringQueryUtil(elasticsearchConverter.getConversionService())
|
||||||
|
.replacePlaceholders(rawString, parameterAccessor);
|
||||||
|
highlightQuery = new StringQuery(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightParameters highlightParameters = HighlightParameters.builder() //
|
||||||
|
.withBoundaryChars(parameters.boundaryChars()) //
|
||||||
|
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
|
||||||
|
.withBoundaryScanner(parameters.boundaryScanner()) //
|
||||||
|
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
|
||||||
|
.withEncoder(parameters.encoder()) //
|
||||||
|
.withForceSource(parameters.forceSource()) //
|
||||||
|
.withFragmenter(parameters.fragmenter()) //
|
||||||
|
.withFragmentSize(parameters.fragmentSize()) //
|
||||||
|
.withNoMatchSize(parameters.noMatchSize()) //
|
||||||
|
.withNumberOfFragments(parameters.numberOfFragments()) //
|
||||||
|
.withHighlightQuery(highlightQuery) //
|
||||||
|
.withOrder(parameters.order()) //
|
||||||
|
.withPhraseLimit(parameters.phraseLimit()) //
|
||||||
|
.withPreTags(parameters.preTags()) //
|
||||||
|
.withPostTags(parameters.postTags()) //
|
||||||
|
.withRequireFieldMatch(parameters.requireFieldMatch()) //
|
||||||
|
.withTagsSchema(parameters.tagsSchema()) //
|
||||||
|
.withType(parameters.type()) //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
|
||||||
|
.map(HighlightField::of) //
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return new Highlight(highlightParameters, highlightFields);
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,6 @@ import java.util.stream.Stream;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.DisabledIf;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.annotation.Version;
|
import org.springframework.data.annotation.Version;
|
||||||
@ -46,9 +45,9 @@ import org.springframework.data.elasticsearch.annotations.Document;
|
|||||||
import org.springframework.data.elasticsearch.annotations.Field;
|
import org.springframework.data.elasticsearch.annotations.Field;
|
||||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||||
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
|
||||||
import org.springframework.data.elasticsearch.annotations.Query;
|
import org.springframework.data.elasticsearch.annotations.Query;
|
||||||
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
||||||
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
@ -76,6 +75,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @author Rasmus Faber-Espensen
|
* @author Rasmus Faber-Espensen
|
||||||
* @author James Mudd
|
* @author James Mudd
|
||||||
|
* @author Haibo Liu
|
||||||
*/
|
*/
|
||||||
@SpringIntegrationTest
|
@SpringIntegrationTest
|
||||||
public abstract class CustomMethodRepositoryIntegrationTests {
|
public abstract class CustomMethodRepositoryIntegrationTests {
|
||||||
@ -1548,6 +1548,26 @@ public abstract class CustomMethodRepositoryIntegrationTests {
|
|||||||
assertThat(searchHit.getHighlightField("type")).hasSize(1).contains("<em>abc</em>");
|
assertThat(searchHit.getHighlightField("type")).hasSize(1).contains("<em>abc</em>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnDifferentHighlightsOnAnnotatedStringQueryMethod() {
|
||||||
|
List<SampleEntity> entities = createSampleEntities("abc xyz", 2);
|
||||||
|
repository.saveAll(entities);
|
||||||
|
|
||||||
|
// when
|
||||||
|
SearchHits<SampleEntity> highlightAbcHits = repository.queryByStringWithSeparateHighlight("abc", "abc");
|
||||||
|
|
||||||
|
assertThat(highlightAbcHits.getTotalHits()).isEqualTo(2);
|
||||||
|
SearchHit<SampleEntity> highlightAbcHit = highlightAbcHits.getSearchHit(0);
|
||||||
|
assertThat(highlightAbcHit.getHighlightField("type")).hasSize(1).contains("<em>abc</em> xyz");
|
||||||
|
|
||||||
|
// when
|
||||||
|
SearchHits<SampleEntity> highlightXyzHits = repository.queryByStringWithSeparateHighlight("abc", "xyz");
|
||||||
|
|
||||||
|
assertThat(highlightXyzHits.getTotalHits()).isEqualTo(2);
|
||||||
|
SearchHit<SampleEntity> highlightXyzHit = highlightXyzHits.getSearchHit(0);
|
||||||
|
assertThat(highlightXyzHit.getHighlightField("type")).hasSize(1).contains("abc <em>xyz</em>");
|
||||||
|
}
|
||||||
|
|
||||||
@Test // DATAES-734
|
@Test // DATAES-734
|
||||||
void shouldUseGeoSortParameter() {
|
void shouldUseGeoSortParameter() {
|
||||||
GeoPoint munich = new GeoPoint(48.137154, 11.5761247);
|
GeoPoint munich = new GeoPoint(48.137154, 11.5761247);
|
||||||
@ -1920,6 +1940,41 @@ public abstract class CustomMethodRepositoryIntegrationTests {
|
|||||||
@Highlight(fields = { @HighlightField(name = "type") })
|
@Highlight(fields = { @HighlightField(name = "type") })
|
||||||
SearchHits<SampleEntity> queryByString(String type);
|
SearchHits<SampleEntity> queryByString(String type);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
{
|
||||||
|
"bool":{
|
||||||
|
"must":[
|
||||||
|
{
|
||||||
|
"match":{
|
||||||
|
"type":"?0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
@Highlight(
|
||||||
|
fields = {@HighlightField(name = "type")},
|
||||||
|
parameters = @HighlightParameters(
|
||||||
|
highlightQuery = @Query("""
|
||||||
|
{
|
||||||
|
"bool":{
|
||||||
|
"must":[
|
||||||
|
{
|
||||||
|
"match":{
|
||||||
|
"type":"?1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SearchHits<SampleEntity> queryByStringWithSeparateHighlight(String type, String highlight);
|
||||||
|
|
||||||
List<SearchHit<SampleEntity>> queryByMessage(String message);
|
List<SearchHit<SampleEntity>> queryByMessage(String message);
|
||||||
|
|
||||||
Stream<SearchHit<SampleEntity>> readByMessage(String message);
|
Stream<SearchHit<SampleEntity>> readByMessage(String message);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user