Skip to content

Commit 9691d61

Browse files
committed
DATAMONGO-1854 - Use ParameterBindingDocumentCodec to parse collation expressions.
1 parent 0151f56 commit 9691d61

14 files changed

+112
-26
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.repository.query;
1717

1818
import org.bson.Document;
19+
1920
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
2021
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
2122
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
@@ -27,8 +28,10 @@
2728
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution;
2829
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution;
2930
import org.springframework.data.repository.query.ParameterAccessor;
31+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3032
import org.springframework.data.repository.query.RepositoryQuery;
3133
import org.springframework.data.repository.query.ResultProcessor;
34+
import org.springframework.expression.spel.standard.SpelExpressionParser;
3235
import org.springframework.util.Assert;
3336

3437
/**
@@ -44,17 +47,24 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
4447
private final MongoQueryMethod method;
4548
private final MongoOperations operations;
4649
private final ExecutableFind<?> executableFind;
50+
private final SpelExpressionParser expressionParser;
51+
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
4752

4853
/**
4954
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
5055
*
5156
* @param method must not be {@literal null}.
5257
* @param operations must not be {@literal null}.
58+
* @param expressionParser must not be {@literal null}.
59+
* @param evaluationContextProvider must not be {@literal null}.
5360
*/
54-
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations) {
61+
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, SpelExpressionParser expressionParser,
62+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
5563

5664
Assert.notNull(operations, "MongoOperations must not be null!");
5765
Assert.notNull(method, "MongoQueryMethod must not be null!");
66+
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");
67+
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!");
5868

5969
this.method = method;
6070
this.operations = operations;
@@ -63,6 +73,8 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations) {
6373
Class<?> type = metadata.getCollectionEntity().getType();
6474

6575
this.executableFind = operations.query(type);
76+
this.expressionParser = expressionParser;
77+
this.evaluationContextProvider = evaluationContextProvider;
6678
}
6779

6880
/*
@@ -167,7 +179,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
167179
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
168180

169181
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
170-
accessor);
182+
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
171183
}
172184

173185
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.bson.Document;
2222
import org.reactivestreams.Publisher;
23+
2324
import org.springframework.core.convert.converter.Converter;
2425
import org.springframework.data.convert.EntityInstantiators;
2526
import org.springframework.data.mongodb.core.MongoOperations;
@@ -33,8 +34,10 @@
3334
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingConverter;
3435
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingExecution;
3536
import org.springframework.data.repository.query.ParameterAccessor;
37+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3638
import org.springframework.data.repository.query.RepositoryQuery;
3739
import org.springframework.data.repository.query.ResultProcessor;
40+
import org.springframework.expression.spel.standard.SpelExpressionParser;
3841
import org.springframework.util.Assert;
3942

4043
/**
@@ -50,22 +53,31 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
5053
private final ReactiveMongoOperations operations;
5154
private final EntityInstantiators instantiators;
5255
private final FindWithProjection<?> findOperationWithProjection;
56+
private final SpelExpressionParser expressionParser;
57+
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
5358

5459
/**
5560
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
5661
* {@link MongoOperations}.
5762
*
5863
* @param method must not be {@literal null}.
5964
* @param operations must not be {@literal null}.
65+
* @param expressionParser must not be {@literal null}.
66+
* @param evaluationContextProvider must not be {@literal null}.
6067
*/
61-
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations) {
68+
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
69+
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
6270

6371
Assert.notNull(method, "MongoQueryMethod must not be null!");
6472
Assert.notNull(operations, "ReactiveMongoOperations must not be null!");
73+
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");
74+
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!");
6575

6676
this.method = method;
6777
this.operations = operations;
6878
this.instantiators = new EntityInstantiators();
79+
this.expressionParser = expressionParser;
80+
this.evaluationContextProvider = evaluationContextProvider;
6981

7082
MongoEntityMetadata<?> metadata = method.getEntityInformation();
7183
Class<?> type = metadata.getCollectionEntity().getType();
@@ -105,7 +117,8 @@ private Object executeDeferred(Object[] parameters) {
105117

106118
private Object execute(MongoParameterAccessor parameterAccessor) {
107119

108-
ConvertingParameterAccessor convertingParamterAccessor = new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor);
120+
ConvertingParameterAccessor convertingParamterAccessor = new ConvertingParameterAccessor(operations.getConverter(),
121+
parameterAccessor);
109122
Query query = createQuery(convertingParamterAccessor);
110123

111124
applyQueryMetaAttributesWhenPresent(query);
@@ -209,7 +222,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
209222
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
210223

211224
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
212-
accessor);
225+
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
213226
}
214227

215228
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.bson.Document;
1919
import org.bson.json.JsonParseException;
20+
2021
import org.springframework.data.mapping.context.MappingContext;
2122
import org.springframework.data.mongodb.core.MongoOperations;
2223
import org.springframework.data.mongodb.core.MongoTemplate;
@@ -26,10 +27,12 @@
2627
import org.springframework.data.mongodb.core.query.Query;
2728
import org.springframework.data.mongodb.core.query.TextCriteria;
2829
import org.springframework.data.repository.query.QueryMethod;
30+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2931
import org.springframework.data.repository.query.RepositoryQuery;
3032
import org.springframework.data.repository.query.ResultProcessor;
3133
import org.springframework.data.repository.query.ReturnedType;
3234
import org.springframework.data.repository.query.parser.PartTree;
35+
import org.springframework.expression.spel.standard.SpelExpressionParser;
3336
import org.springframework.util.StringUtils;
3437

3538
/**
@@ -52,10 +55,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
5255
*
5356
* @param method must not be {@literal null}.
5457
* @param mongoOperations must not be {@literal null}.
58+
* @param expressionParser must not be {@literal null}.
59+
* @param evaluationContextProvider must not be {@literal null}.
5560
*/
56-
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations) {
61+
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
62+
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
5763

58-
super(method, mongoOperations);
64+
super(method, mongoOperations, expressionParser, evaluationContextProvider);
5965

6066
this.processor = method.getResultProcessor();
6167
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,32 @@
2121

2222
import org.aopalliance.intercept.MethodInterceptor;
2323
import org.bson.Document;
24+
2425
import org.springframework.aop.framework.ProxyFactory;
2526
import org.springframework.data.mongodb.core.query.Collation;
2627
import org.springframework.data.mongodb.core.query.Query;
28+
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
29+
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
30+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
31+
import org.springframework.expression.spel.standard.SpelExpressionParser;
2732
import org.springframework.lang.Nullable;
2833
import org.springframework.util.NumberUtils;
2934
import org.springframework.util.ObjectUtils;
35+
import org.springframework.util.StringUtils;
3036

3137
/**
3238
* Internal utility class to help avoid duplicate code required in both the reactive and the sync {@link Query} support
3339
* offered by repositories.
3440
*
3541
* @author Christoph Strobl
42+
* @author Mark Paluch
3643
* @since 2.1
3744
* @currentRead Assassin's Apprentice - Robin Hobb
3845
*/
3946
class QueryUtils {
4047

48+
private static final ParameterBindingDocumentCodec CODEC = new ParameterBindingDocumentCodec();
49+
4150
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
4251

4352
/**
@@ -80,7 +89,9 @@ static Query decorateSort(Query query, Document defaultSort) {
8089
* @see Query#collation(Collation)
8190
* @since 2.2
8291
*/
83-
static Query applyCollation(Query query, @Nullable String collationExpression, ConvertingParameterAccessor accessor) {
92+
static Query applyCollation(Query query, @Nullable String collationExpression, ConvertingParameterAccessor accessor,
93+
MongoParameters parameters, SpelExpressionParser expressionParser,
94+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
8495

8596
if (accessor.getCollation() != null) {
8697
return query.collation(accessor.getCollation());
@@ -90,10 +101,15 @@ static Query applyCollation(Query query, @Nullable String collationExpression, C
90101
return query;
91102
}
92103

93-
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(collationExpression);
104+
if (StringUtils.trimLeadingWhitespace(collationExpression).startsWith("{")) {
94105

95-
// TODO: use parameter binding Parser instead of Document.parse once DATAMONGO-2199 is merged.
106+
ParameterBindingContext bindingContext = new ParameterBindingContext((accessor::getBindableValue),
107+
expressionParser, evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues()));
96108

109+
return query.collation(Collation.from(CODEC.decode(collationExpression, bindingContext)));
110+
}
111+
112+
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(collationExpression);
97113
if (!matcher.find()) {
98114
return query.collation(Collation.parse(collationExpression));
99115
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.bson.Document;
1919
import org.bson.json.JsonParseException;
20+
2021
import org.springframework.data.mapping.context.MappingContext;
2122
import org.springframework.data.mongodb.core.MongoTemplate;
2223
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
@@ -25,10 +26,12 @@
2526
import org.springframework.data.mongodb.core.query.Query;
2627
import org.springframework.data.mongodb.core.query.TextCriteria;
2728
import org.springframework.data.repository.query.QueryMethod;
29+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2830
import org.springframework.data.repository.query.RepositoryQuery;
2931
import org.springframework.data.repository.query.ResultProcessor;
3032
import org.springframework.data.repository.query.ReturnedType;
3133
import org.springframework.data.repository.query.parser.PartTree;
34+
import org.springframework.expression.spel.standard.SpelExpressionParser;
3235
import org.springframework.util.StringUtils;
3336

3437
/**
@@ -50,10 +53,13 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
5053
*
5154
* @param method must not be {@literal null}.
5255
* @param mongoOperations must not be {@literal null}.
56+
* @param expressionParser must not be {@literal null}.
57+
* @param evaluationContextProvider must not be {@literal null}.
5358
*/
54-
public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations) {
59+
public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
60+
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
5561

56-
super(method, mongoOperations);
62+
super(method, mongoOperations, expressionParser, evaluationContextProvider);
5763

5864
this.processor = method.getResultProcessor();
5965
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.bson.Document;
1919
import org.slf4j.Logger;
2020
import org.slf4j.LoggerFactory;
21+
2122
import org.springframework.data.mongodb.core.MongoOperations;
2223
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
2324
import org.springframework.data.mongodb.core.query.BasicQuery;
@@ -78,7 +79,7 @@ public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod meth
7879
ReactiveMongoOperations mongoOperations, SpelExpressionParser expressionParser,
7980
QueryMethodEvaluationContextProvider evaluationContextProvider) {
8081

81-
super(method, mongoOperations);
82+
super(method, mongoOperations, expressionParser, evaluationContextProvider);
8283

8384
Assert.notNull(query, "Query must not be null!");
8485
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.bson.Document;
1919
import org.slf4j.Logger;
2020
import org.slf4j.LoggerFactory;
21+
2122
import org.springframework.data.mongodb.core.MongoOperations;
2223
import org.springframework.data.mongodb.core.query.BasicQuery;
2324
import org.springframework.data.mongodb.core.query.Query;
@@ -77,7 +78,7 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera
7778
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations,
7879
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
7980

80-
super(method, mongoOperations);
81+
super(method, mongoOperations, expressionParser, evaluationContextProvider);
8182

8283
Assert.notNull(query, "Query must not be null!");
8384
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.slf4j.Logger;
2323
import org.slf4j.LoggerFactory;
24+
2425
import org.springframework.data.domain.Sort;
2526
import org.springframework.data.domain.Sort.Direction;
2627
import org.springframework.data.domain.Sort.Order;
@@ -96,7 +97,6 @@ public void onCreation(PartTreeMongoQuery query) {
9697

9798
if (query.getQueryMethod().hasAnnotatedCollation()) {
9899

99-
// TODO: SPEL
100100
String collation = query.getQueryMethod().getAnnotatedCollation();
101101
if (!collation.contains("?")) {
102102
index = index.collation(Collation.parse(collation));

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
190190
} else if (queryMethod.hasAnnotatedQuery()) {
191191
return new StringBasedMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
192192
} else {
193-
return new PartTreeMongoQuery(queryMethod, operations);
193+
return new PartTreeMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
194194
}
195195
}
196196
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
180180
} else if (queryMethod.hasAnnotatedQuery()) {
181181
return new ReactiveStringBasedMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
182182
} else {
183-
return new ReactivePartTreeMongoQuery(queryMethod, operations);
183+
return new ReactivePartTreeMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
184184
}
185185
}
186186
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
package org.springframework.data.mongodb.repository.query;
1717

1818
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.ArgumentMatchers.any;
20-
import static org.mockito.ArgumentMatchers.anyString;
21-
import static org.mockito.ArgumentMatchers.eq;
19+
import static org.mockito.ArgumentMatchers.*;
2220
import static org.mockito.Mockito.*;
2321

2422
import java.lang.reflect.Method;
@@ -35,6 +33,7 @@
3533
import org.mockito.Mock;
3634
import org.mockito.Mockito;
3735
import org.mockito.junit.MockitoJUnitRunner;
36+
3837
import org.springframework.data.domain.Page;
3938
import org.springframework.data.domain.PageRequest;
4039
import org.springframework.data.domain.Pageable;
@@ -60,6 +59,8 @@
6059
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
6160
import org.springframework.data.repository.Repository;
6261
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
62+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
63+
import org.springframework.expression.spel.standard.SpelExpressionParser;
6364

6465
import com.mongodb.client.result.DeleteResult;
6566

@@ -455,7 +456,7 @@ private static class MongoQueryFake extends AbstractMongoQuery {
455456
private boolean isLimitingQuery;
456457

457458
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
458-
super(method, operations);
459+
super(method, operations, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT);
459460
}
460461

461462
@Override

0 commit comments

Comments
 (0)