Skip to content

Commit 6af099e

Browse files
authored
Add SpEL support for highlight query and source filter.
Original Pull Request spring-projects#2853 Closes spring-projects#2852
1 parent 96185f9 commit 6af099e

17 files changed

+445
-84
lines changed

src/main/java/org/springframework/data/elasticsearch/core/document/Document.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@
3030
import org.springframework.util.Assert;
3131

3232
/**
33-
* A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original
33+
* A representation of an Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original
3434
* insertion order.
3535
* <p>
3636
* Document does not allow {@code null} keys. It allows {@literal null} values.
3737
* <p>
38-
* Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw
38+
* Implementing classes can be either mutable or immutable. In case a subclass is immutable, its methods may throw
3939
* {@link UnsupportedOperationException} when calling modifying methods.
4040
*
4141
* @author Mark Paluch

src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.data.elasticsearch.core.query.Query;
2626
import org.springframework.data.repository.query.ParametersParameterAccessor;
2727
import org.springframework.data.repository.query.QueryMethod;
28+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2829
import org.springframework.data.repository.query.RepositoryQuery;
2930
import org.springframework.data.repository.query.ResultProcessor;
3031
import org.springframework.data.util.StreamUtils;
@@ -47,12 +48,15 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
4748
protected ElasticsearchQueryMethod queryMethod;
4849
protected final ElasticsearchOperations elasticsearchOperations;
4950
protected final ElasticsearchConverter elasticsearchConverter;
51+
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
5052

5153
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
52-
ElasticsearchOperations elasticsearchOperations) {
54+
ElasticsearchOperations elasticsearchOperations,
55+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
5356
this.queryMethod = queryMethod;
5457
this.elasticsearchOperations = elasticsearchOperations;
5558
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
59+
this.evaluationContextProvider = evaluationContextProvider;
5660
}
5761

5862
@Override
@@ -128,7 +132,8 @@ public Query createQuery(Object[] parameters) {
128132
var query = createQuery(parameterAccessor);
129133
Assert.notNull(query, "unsupported query");
130134

131-
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
135+
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
136+
evaluationContextProvider);
132137

133138
return query;
134139
}

src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.data.mapping.context.MappingContext;
3535
import org.springframework.data.repository.query.ParameterAccessor;
3636
import org.springframework.data.repository.query.QueryMethod;
37+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3738
import org.springframework.data.repository.query.RepositoryQuery;
3839
import org.springframework.data.repository.query.ResultProcessor;
3940
import org.springframework.util.Assert;
@@ -50,12 +51,15 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
5051

5152
protected final ReactiveElasticsearchQueryMethod queryMethod;
5253
private final ReactiveElasticsearchOperations elasticsearchOperations;
54+
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
5355

5456
AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod,
55-
ReactiveElasticsearchOperations elasticsearchOperations) {
57+
ReactiveElasticsearchOperations elasticsearchOperations,
58+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
5659

5760
this.queryMethod = queryMethod;
5861
this.elasticsearchOperations = elasticsearchOperations;
62+
this.evaluationContextProvider = evaluationContextProvider;
5963
}
6064

6165
/*
@@ -96,7 +100,8 @@ private Object execute(ElasticsearchParametersParameterAccessor parameterAccesso
96100
var query = createQuery(parameterAccessor);
97101
Assert.notNull(query, "unsupported query");
98102

99-
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
103+
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
104+
evaluationContextProvider);
100105

101106
String indexName = queryMethod.getEntityInformation().getIndexName();
102107
IndexCoordinates index = IndexCoordinates.of(indexName);

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
1919
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
2020
import org.springframework.data.elasticsearch.core.query.BaseQuery;
21-
import org.springframework.data.elasticsearch.core.query.Query;
22-
import org.springframework.data.elasticsearch.core.query.RuntimeField;
23-
import org.springframework.data.elasticsearch.core.query.ScriptedField;
2421
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
2522
import org.springframework.data.mapping.context.MappingContext;
23+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2624
import org.springframework.data.repository.query.parser.PartTree;
2725

2826
/**
@@ -34,14 +32,16 @@
3432
* @author Mark Paluch
3533
* @author Rasmus Faber-Espensen
3634
* @author Peter-Josef Meisch
35+
* @author Haibo Liu
3736
*/
3837
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
3938

4039
private final PartTree tree;
4140
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
4241

43-
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
44-
super(method, elasticsearchOperations);
42+
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
43+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
44+
super(method, elasticsearchOperations, evaluationContextProvider);
4545
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
4646
this.mappingContext = elasticsearchConverter.getMappingContext();
4747
}

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.stream.Stream;
2525

2626
import org.springframework.core.annotation.AnnotatedElementUtils;
27+
import org.springframework.core.convert.ConversionService;
2728
import org.springframework.dao.InvalidDataAccessApiUsageException;
2829
import org.springframework.data.elasticsearch.annotations.Highlight;
2930
import org.springframework.data.elasticsearch.annotations.Query;
@@ -41,13 +42,13 @@
4142
import org.springframework.data.elasticsearch.core.query.RuntimeField;
4243
import org.springframework.data.elasticsearch.core.query.ScriptedField;
4344
import org.springframework.data.elasticsearch.core.query.SourceFilter;
44-
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
45+
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
4546
import org.springframework.data.mapping.context.MappingContext;
4647
import org.springframework.data.projection.ProjectionFactory;
4748
import org.springframework.data.repository.core.RepositoryMetadata;
48-
import org.springframework.data.repository.query.ParameterAccessor;
4949
import org.springframework.data.repository.query.Parameters;
5050
import org.springframework.data.repository.query.QueryMethod;
51+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
5152
import org.springframework.data.repository.util.QueryExecutionConverters;
5253
import org.springframework.data.repository.util.ReactiveWrapperConverters;
5354
import org.springframework.data.util.TypeInformation;
@@ -146,9 +147,7 @@ public HighlightQuery getAnnotatedHighlightQuery(HighlightConverter highlightCon
146147
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
147148
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
148149

149-
return new HighlightQuery(
150-
highlightConverter.convert(highlightAnnotation),
151-
getDomainClass());
150+
return new HighlightQuery(highlightConverter.convert(highlightAnnotation), getDomainClass());
152151
}
153152

154153
/**
@@ -288,42 +287,46 @@ public boolean hasCountQueryAnnotation() {
288287
* @param parameterAccessor the accessor with the query method parameter details
289288
* @param converter {@link ElasticsearchConverter} needed to convert entity property names to the Elasticsearch field
290289
* names and for parameter conversion when the includes or excludes are defined as parameters
290+
* @param evaluationContextProvider to provide an evaluation context for SpEL evaluation
291291
* @return source filter with includes and excludes for a query, {@literal null} when no {@link SourceFilters}
292292
* annotation was set on the method.
293293
* @since 5.0
294294
*/
295295
@Nullable
296-
SourceFilter getSourceFilter(ParameterAccessor parameterAccessor, ElasticsearchConverter converter) {
296+
SourceFilter getSourceFilter(ElasticsearchParametersParameterAccessor parameterAccessor,
297+
ElasticsearchConverter converter,
298+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
297299

298300
if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) {
299301
return null;
300302
}
301303

302-
StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
304+
ConversionService conversionService = converter.getConversionService();
303305
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
304306

305307
if (sourceFilters.includes().length > 0) {
306-
fetchSourceFilterBuilder
307-
.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil));
308+
fetchSourceFilterBuilder.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor,
309+
conversionService, evaluationContextProvider));
308310
}
309311

310312
if (sourceFilters.excludes().length > 0) {
311-
fetchSourceFilterBuilder
312-
.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil));
313+
fetchSourceFilterBuilder.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor,
314+
conversionService, evaluationContextProvider));
313315
}
314316

315317
return fetchSourceFilterBuilder.build();
316318
}
317319

318-
private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor,
319-
StringQueryUtil stringQueryUtil) {
320+
private String[] mapParameters(String[] source, ElasticsearchParametersParameterAccessor parameterAccessor,
321+
ConversionService conversionService, QueryMethodEvaluationContextProvider evaluationContextProvider) {
320322

321323
List<String> fieldNames = new ArrayList<>();
322324

323325
for (String s : source) {
324326

325327
if (!s.isBlank()) {
326-
String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
328+
String fieldName = new QueryStringProcessor(s, this, conversionService, evaluationContextProvider)
329+
.createQuery(parameterAccessor);
327330
// this could be "[\"foo\",\"bar\"]", must be split
328331
if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
329332
// noinspection RegExpRedundantEscape
@@ -367,14 +370,16 @@ private Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadat
367370
}
368371

369372
void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
370-
ElasticsearchConverter elasticsearchConverter) {
373+
ElasticsearchConverter elasticsearchConverter,
374+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
371375

372376
if (hasAnnotatedHighlight()) {
373-
query.setHighlightQuery(
374-
getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor, elasticsearchConverter)));
377+
var highlightQuery = getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor,
378+
elasticsearchConverter.getConversionService(), evaluationContextProvider, this));
379+
query.setHighlightQuery(highlightQuery);
375380
}
376381

377-
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
382+
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter, evaluationContextProvider);
378383
if (sourceFilter != null) {
379384
query.addSourceFilter(sourceFilter);
380385
}

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
2020
import org.springframework.data.elasticsearch.core.query.BaseQuery;
2121
import org.springframework.data.elasticsearch.core.query.StringQuery;
22-
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
23-
import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator;
22+
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
2423
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2524
import org.springframework.util.Assert;
2625

@@ -37,17 +36,15 @@
3736
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
3837

3938
private final String queryString;
40-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
4139

4240
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
4341
String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) {
44-
super(queryMethod, elasticsearchOperations);
42+
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
4543

4644
Assert.notNull(queryString, "Query cannot be empty");
4745
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
4846

4947
this.queryString = queryString;
50-
this.evaluationContextProvider = evaluationContextProvider;
5148
}
5249

5350
@Override
@@ -66,15 +63,12 @@ protected boolean isExistsQuery() {
6663
}
6764

6865
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
69-
7066
ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
71-
var replacedString = new StringQueryUtil(conversionService).replacePlaceholders(this.queryString,
72-
parameterAccessor);
73-
var evaluator = new QueryStringSpELEvaluator(replacedString, parameterAccessor, queryMethod,
74-
evaluationContextProvider, conversionService);
75-
var query = new StringQuery(evaluator.evaluate());
76-
query.addSort(parameterAccessor.getSort());
77-
return query;
67+
var processed = new QueryStringProcessor(queryString, queryMethod, conversionService, evaluationContextProvider)
68+
.createQuery(parameterAccessor);
69+
70+
return new StringQuery(processed)
71+
.addSort(parameterAccessor.getSort());
7872
}
7973

8074
}

src/main/java/org/springframework/data/elasticsearch/repository/query/HighlightConverter.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import java.util.Arrays;
1919
import java.util.List;
2020

21-
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
21+
import org.springframework.core.convert.ConversionService;
2222
import org.springframework.data.elasticsearch.core.query.Query;
2323
import org.springframework.data.elasticsearch.core.query.StringQuery;
2424
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
2525
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
2626
import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
27-
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
27+
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
28+
import org.springframework.data.repository.query.QueryMethod;
29+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2830
import org.springframework.util.Assert;
2931

3032
/**
@@ -35,12 +37,24 @@
3537
public class HighlightConverter {
3638

3739
private final ElasticsearchParametersParameterAccessor parameterAccessor;
38-
private final ElasticsearchConverter elasticsearchConverter;
40+
private final ConversionService conversionService;
41+
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
42+
private final QueryMethod queryMethod;
3943

4044
HighlightConverter(ElasticsearchParametersParameterAccessor parameterAccessor,
41-
ElasticsearchConverter elasticsearchConverter) {
45+
ConversionService conversionService,
46+
QueryMethodEvaluationContextProvider evaluationContextProvider,
47+
QueryMethod queryMethod) {
48+
49+
Assert.notNull(parameterAccessor, "parameterAccessor must not be null");
50+
Assert.notNull(conversionService, "conversionService must not be null");
51+
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
52+
Assert.notNull(queryMethod, "queryMethod must not be null");
53+
4254
this.parameterAccessor = parameterAccessor;
43-
this.elasticsearchConverter = elasticsearchConverter;
55+
this.conversionService = conversionService;
56+
this.evaluationContextProvider = evaluationContextProvider;
57+
this.queryMethod = queryMethod;
4458
}
4559

4660
/**
@@ -58,10 +72,10 @@ Highlight convert(org.springframework.data.elasticsearch.annotations.Highlight h
5872
// replace placeholders in highlight query with actual parameters
5973
Query highlightQuery = null;
6074
if (!parameters.highlightQuery().value().isEmpty()) {
61-
String rawString = parameters.highlightQuery().value();
62-
String queryString = new StringQueryUtil(elasticsearchConverter.getConversionService())
63-
.replacePlaceholders(rawString, parameterAccessor);
64-
highlightQuery = new StringQuery(queryString);
75+
String rawQuery = parameters.highlightQuery().value();
76+
String query = new QueryStringProcessor(rawQuery, queryMethod, conversionService, evaluationContextProvider)
77+
.createQuery(parameterAccessor);
78+
highlightQuery = new StringQuery(query);
6579
}
6680

6781
HighlightParameters highlightParameters = HighlightParameters.builder() //

0 commit comments

Comments
 (0)