Polishing.

This commit is contained in:
Peter-Josef Meisch 2024-01-31 20:43:26 +01:00
parent b391a4e844
commit 6390aaa739
No known key found for this signature in database
GPG Key ID: DE108246970C7708
8 changed files with 44 additions and 18 deletions

View File

@ -42,6 +42,7 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations, public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) { String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) {
super(queryMethod, elasticsearchOperations); super(queryMethod, elasticsearchOperations);
Assert.notNull(queryString, "Query cannot be empty"); Assert.notNull(queryString, "Query cannot be empty");
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null"); Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
@ -65,11 +66,11 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
} }
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) { protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
String queryString = new StringQueryUtil(conversionService)
.replacePlaceholders(this.queryString, parameterAccessor);
QueryStringSpELEvaluator evaluator = new QueryStringSpELEvaluator(queryString, parameterAccessor, queryMethod, ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
var replacedString = new StringQueryUtil(conversionService).replacePlaceholders(this.queryString,
parameterAccessor);
var evaluator = new QueryStringSpELEvaluator(replacedString, parameterAccessor, queryMethod,
evaluationContextProvider, conversionService); evaluationContextProvider, conversionService);
var query = new StringQuery(evaluator.evaluate()); var query = new StringQuery(evaluator.evaluate());
query.addSort(parameterAccessor.getSort()); query.addSort(parameterAccessor.getSort());

View File

@ -23,6 +23,7 @@ import org.springframework.data.elasticsearch.repository.support.StringQueryUtil
import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator; import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
@ -45,8 +46,11 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea
public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod, public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod,
ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser, ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser,
QueryMethodEvaluationContextProvider evaluationContextProvider) { QueryMethodEvaluationContextProvider evaluationContextProvider) {
super(queryMethod, operations); super(queryMethod, operations);
Assert.notNull(query, "query must not be null");
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
this.query = query; this.query = query;
this.evaluationContextProvider = evaluationContextProvider; this.evaluationContextProvider = evaluationContextProvider;
} }

View File

@ -22,6 +22,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.data.elasticsearch.core.convert.ConversionException; import org.springframework.data.elasticsearch.core.convert.ConversionException;
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchQueryValueConversionService; import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchQueryValueConversionService;
import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils; import org.springframework.util.NumberUtils;
/** /**
@ -36,6 +37,9 @@ final public class StringQueryUtil {
private final ConversionService conversionService; private final ConversionService conversionService;
public StringQueryUtil(ConversionService conversionService) { public StringQueryUtil(ConversionService conversionService) {
Assert.notNull(conversionService, "conversionService must not be null");
this.conversionService = ElasticsearchQueryValueConversionService.getInstance(conversionService); this.conversionService = ElasticsearchQueryValueConversionService.getInstance(conversionService);
} }
@ -43,8 +47,8 @@ final public class StringQueryUtil {
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
String result = input; String result = input;
while (matcher.find()) {
while (matcher.find()) {
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)"; String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class); int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
String replacement = Matcher.quoteReplacement(getParameterWithIndex(accessor, index, input)); String replacement = Matcher.quoteReplacement(getParameterWithIndex(accessor, index, input));
@ -59,7 +63,8 @@ final public class StringQueryUtil {
private String getParameterWithIndex(ParameterAccessor accessor, int index, String input) { private String getParameterWithIndex(ParameterAccessor accessor, int index, String input) {
Object parameter = accessor.getBindableValue(index); Object parameter = accessor.getBindableValue(index);
String value = conversionService.convert(parameter, String.class); String value = conversionService.convert(parameter, String.class);
if (value == null) { if (value == null) {
throw new ConversionException(String.format( throw new ConversionException(String.format(
"Parameter value can't be null for placeholder at index '%s' in query '%s' when querying elasticsearch", "Parameter value can't be null for placeholder at index '%s' in query '%s' when querying elasticsearch",

View File

@ -21,8 +21,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.data.elasticsearch.core.convert.ConversionException; import org.springframework.data.elasticsearch.core.convert.ConversionException;
import org.springframework.data.elasticsearch.repository.query.ElasticsearchParametersParameterAccessor; import org.springframework.data.elasticsearch.repository.query.ElasticsearchParametersParameterAccessor;
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchQueryValueConversionService;
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchCollectionValueToStringConverter; import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchCollectionValueToStringConverter;
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchQueryValueConversionService;
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchStringValueToStringConverter; import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchStringValueToStringConverter;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
@ -59,6 +59,13 @@ public class QueryStringSpELEvaluator {
public QueryStringSpELEvaluator(String queryString, ElasticsearchParametersParameterAccessor parameterAccessor, public QueryStringSpELEvaluator(String queryString, ElasticsearchParametersParameterAccessor parameterAccessor,
QueryMethod queryMethod, QueryMethodEvaluationContextProvider evaluationContextProvider, QueryMethod queryMethod, QueryMethodEvaluationContextProvider evaluationContextProvider,
ConversionService conversionService) { ConversionService conversionService) {
Assert.notNull(queryString, "queryString must not be null");
Assert.notNull(parameterAccessor, "parameterAccessor must not be null");
Assert.notNull(queryMethod, "queryMethod must not be null");
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
Assert.notNull(conversionService, "conversionService must not be null");
this.queryString = queryString; this.queryString = queryString;
this.parameterAccessor = parameterAccessor; this.parameterAccessor = parameterAccessor;
this.queryMethod = queryMethod; this.queryMethod = queryMethod;
@ -74,9 +81,11 @@ public class QueryStringSpELEvaluator {
*/ */
public String evaluate() { public String evaluate() {
Expression expr = getQueryExpression(queryString); Expression expr = getQueryExpression(queryString);
if (expr != null) { if (expr != null) {
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameterAccessor.getParameters(), EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameterAccessor.getParameters(),
parameterAccessor.getValues()); parameterAccessor.getValues());
if (context instanceof StandardEvaluationContext standardEvaluationContext) { if (context instanceof StandardEvaluationContext standardEvaluationContext) {
standardEvaluationContext.setTypeConverter(elasticsearchSpELTypeConverter); standardEvaluationContext.setTypeConverter(elasticsearchSpELTypeConverter);
} }
@ -97,12 +106,14 @@ public class QueryStringSpELEvaluator {
*/ */
private String parseExpressions(Expression rootExpr, EvaluationContext context) { private String parseExpressions(Expression rootExpr, EvaluationContext context) {
StringBuilder parsed = new StringBuilder(); StringBuilder parsed = new StringBuilder();
if (rootExpr instanceof LiteralExpression literalExpression) { if (rootExpr instanceof LiteralExpression literalExpression) {
// get the string literal directly // get the string literal directly
parsed.append(literalExpression.getExpressionString()); parsed.append(literalExpression.getExpressionString());
} else if (rootExpr instanceof SpelExpression spelExpression) { } else if (rootExpr instanceof SpelExpression spelExpression) {
// evaluate the value // evaluate the value
String value = spelExpression.getValue(context, String.class); String value = spelExpression.getValue(context, String.class);
if (value == null) { if (value == null) {
throw new ConversionException(String.format( throw new ConversionException(String.format(
"Parameter value can't be null for SpEL expression '%s' in method '%s' when querying elasticsearch", "Parameter value can't be null for SpEL expression '%s' in method '%s' when querying elasticsearch",

View File

@ -70,14 +70,18 @@ public class ElasticsearchCollectionValueToStringConverter implements GenericCon
@Override @Override
@Nullable @Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return "[]"; return "[]";
} }
Collection<?> sourceCollection = (Collection<?>) source; Collection<?> sourceCollection = (Collection<?>) source;
if (sourceCollection.isEmpty()) { if (sourceCollection.isEmpty()) {
return "[]"; return "[]";
} }
StringJoiner sb = new StringJoiner(DELIMITER, "[", "]"); StringJoiner sb = new StringJoiner(DELIMITER, "[", "]");
for (Object sourceElement : sourceCollection) { for (Object sourceElement : sourceCollection) {
// ignore the null value in collection // ignore the null value in collection
if (Objects.isNull(sourceElement)) { if (Objects.isNull(sourceElement)) {
@ -86,8 +90,9 @@ public class ElasticsearchCollectionValueToStringConverter implements GenericCon
Object targetElement = this.conversionService.convert( Object targetElement = this.conversionService.convert(
sourceElement, sourceType.elementTypeDescriptor(sourceElement), targetType); sourceElement, sourceType.elementTypeDescriptor(sourceElement), targetType);
if (sourceElement instanceof String) { if (sourceElement instanceof String) {
sb.add("\"" + targetElement + "\""); sb.add("\"" + targetElement + '"');
} else { } else {
sb.add(String.valueOf(targetElement)); sb.add(String.valueOf(targetElement));
} }

View File

@ -27,7 +27,8 @@ import org.springframework.util.Assert;
/** /**
* A {@link ConversionService} using custom converters to handle query values in elasticsearch query. If the value to be * A {@link ConversionService} using custom converters to handle query values in elasticsearch query. If the value to be
* converted beyond the scope of custom converters, it'll delegate to the {@link #delegate delegated conversion service}. * converted beyond the scope of custom converters, it'll delegate to the {@link #delegate delegated conversion
* service}.
* <p> * <p>
* This is a better solution for converting query values in elasticsearch query, because it has all the capability the * This is a better solution for converting query values in elasticsearch query, because it has all the capability the
* {@link #delegate delegated conversion service} has, especially for user-registered {@link Converter}s. * {@link #delegate delegated conversion service} has, especially for user-registered {@link Converter}s.
@ -44,7 +45,9 @@ public class ElasticsearchQueryValueConversionService implements ConversionServi
private final ConversionService delegate; private final ConversionService delegate;
private ElasticsearchQueryValueConversionService(ConversionService delegate) { private ElasticsearchQueryValueConversionService(ConversionService delegate) {
Assert.notNull(delegate, "delegated ConversionService must not be null"); Assert.notNull(delegate, "delegated ConversionService must not be null");
this.delegate = delegate; this.delegate = delegate;
// register elasticsearch custom type converters for conversion service // register elasticsearch custom type converters for conversion service
@ -85,6 +88,7 @@ public class ElasticsearchQueryValueConversionService implements ConversionServi
@Override @Override
@Nullable @Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
if (valueConversionService.canConvert(sourceType, targetType)) { if (valueConversionService.canConvert(sourceType, targetType)) {
return valueConversionService.convert(source, sourceType, targetType); return valueConversionService.convert(source, sourceType, targetType);
} else { } else {

View File

@ -40,10 +40,7 @@ public class ElasticsearchStringValueToStringConverter implements GenericConvert
@Override @Override
@Nullable @Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { return source != null ? escape(source) : null;
return null;
}
return escape(source);
} }
private String escape(@Nullable Object source) { private String escape(@Nullable Object source) {

View File

@ -2109,8 +2109,8 @@ public abstract class CustomMethodRepositoryIntegrationTests {
SearchHits<SampleEntity> queryByType(String type); SearchHits<SampleEntity> queryByType(String type);
/** /**
* The parameter is annotated with {@link Nullable} deliberately to test that our placeholder parameter will * The parameter is annotated with {@link Nullable} deliberately to test that our placeholder parameter will not
* not accept a null parameter as query value. * accept a null parameter as query value.
*/ */
@Query("{\"bool\": {\"must\": [{\"term\": {\"type\": \"?0\"}}]}}") @Query("{\"bool\": {\"must\": [{\"term\": {\"type\": \"?0\"}}]}}")
@Highlight(fields = { @HighlightField(name = "type") }) @Highlight(fields = { @HighlightField(name = "type") })
@ -2444,8 +2444,7 @@ public abstract class CustomMethodRepositoryIntegrationTests {
} }
static class SampleProperty { static class SampleProperty {
@Nullable @Nullable private String first;
private String first;
@Nullable private String last; @Nullable private String last;
SampleProperty(@Nullable String first, @Nullable String last) { SampleProperty(@Nullable String first, @Nullable String last) {