Support highlight query in @HighlightParameters annotation.

Original Pull Request #2802
This commit is contained in:
puppylpg 2023-12-18 01:16:18 +08:00 committed by GitHub
parent d0ed80dfde
commit 96b38652ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 168 additions and 63 deletions

View File

@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.0
*/
@Documented
@ -59,6 +60,8 @@ public @interface HighlightParameters {
int numberOfFragments() default -1;
Query highlightQuery() default @Query;
String order() default "";
int phraseLimit() default -1;

View File

@ -15,14 +15,13 @@
*/
package org.springframework.data.elasticsearch.core.query.highlight;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.3
*/
public class Highlight {
@ -57,42 +56,4 @@ public class Highlight {
public List<HighlightField> getFields() {
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);
}
}

View File

@ -199,7 +199,7 @@ public abstract class HighlightCommonParameters {
return (SELF) this;
}
public SELF withHighlightQuery(Query highlightQuery) {
public SELF withHighlightQuery(@Nullable Query highlightQuery) {
this.highlightQuery = highlightQuery;
return (SELF) this;
}

View File

@ -15,14 +15,6 @@
*/
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.dao.InvalidDataAccessApiUsageException;
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.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
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
*
@ -66,6 +65,7 @@ import org.springframework.util.ClassUtils;
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Alexander Torres
* @author Haibo Liu
*/
public class ElasticsearchQueryMethod extends QueryMethod {
@ -81,8 +81,6 @@ public class ElasticsearchQueryMethod extends QueryMethod {
@Nullable private ElasticsearchEntityMetadata<?> metadata;
@Nullable private final Query queryAnnotation;
@Nullable private final Highlight highlightAnnotation;
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
@Nullable private final SourceFilters sourceFilters;
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
* @see #hasAnnotatedHighlight()
*/
public HighlightQuery getAnnotatedHighlightQuery() {
public HighlightQuery getAnnotatedHighlightQuery(HighlightConverter highlightConverter) {
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
return highlightQueryLazy.get();
}
private HighlightQuery createAnnotatedHighlightQuery() {
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
return new HighlightQuery(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
highlightConverter.convert(highlightAnnotation),
getDomainClass());
}
@ -378,7 +370,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
ElasticsearchConverter elasticsearchConverter) {
if (hasAnnotatedHighlight()) {
query.setHighlightQuery(getAnnotatedHighlightQuery());
query.setHighlightQuery(getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor, elasticsearchConverter)));
}
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);

View File

@ -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);
}
}

View File

@ -32,7 +32,6 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
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.Highlight;
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.SourceFilters;
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
@ -76,6 +75,7 @@ import org.springframework.lang.Nullable;
* @author Peter-Josef Meisch
* @author Rasmus Faber-Espensen
* @author James Mudd
* @author Haibo Liu
*/
@SpringIntegrationTest
public abstract class CustomMethodRepositoryIntegrationTests {
@ -1548,6 +1548,26 @@ public abstract class CustomMethodRepositoryIntegrationTests {
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
void shouldUseGeoSortParameter() {
GeoPoint munich = new GeoPoint(48.137154, 11.5761247);
@ -1920,6 +1940,41 @@ public abstract class CustomMethodRepositoryIntegrationTests {
@Highlight(fields = { @HighlightField(name = "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);
Stream<SearchHit<SampleEntity>> readByMessage(String message);