Browse Source

DATAMONGO-990 - Add support for SpEL expressions in @Query.

Ported and adapted support for SpEL expressions @Query annotations from Spring Data JPA. StringBasedMongoQuery can now evaluate SpEL fragments in queries with the help of the given EvaluationContextProvider. Introduced EvaluationContextProvider to AbstractMongoQuery. Exposed access to actual parameter values in MongoParameterAccessor.

Original pull request: #285.
pull/299/merge
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
67f638d953
  1. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
  2. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java
  3. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java
  4. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java
  5. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java
  6. 117
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
  7. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java
  8. 19
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  9. 49
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java
  10. 124
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SampleEvaluationContextExtension.java
  11. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
  12. 13
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java
  13. 33
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
  14. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java
  15. 6
      spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml

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

@ -31,6 +31,7 @@ import org.springframework.data.geo.Point; @@ -31,6 +31,7 @@ import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.util.CloseableIterator;
@ -51,20 +52,25 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -51,20 +52,25 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
private final MongoQueryMethod method;
private final MongoOperations operations;
private final EvaluationContextProvider evaluationContextProvider;
/**
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations) {
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations,
EvaluationContextProvider evaluationContextProvider) {
Assert.notNull(operations);
Assert.notNull(method);
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!");
this.method = method;
this.operations = operations;
this.evaluationContextProvider = evaluationContextProvider;
}
/*
@ -158,6 +164,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -158,6 +164,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
*/
protected abstract boolean isDeleteQuery();
public EvaluationContextProvider getEvaluationContextProvider() {
return evaluationContextProvider;
}
private abstract class Execution {
abstract Object execute(Query query);

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

@ -41,6 +41,7 @@ import com.mongodb.DBRef; @@ -41,6 +41,7 @@ import com.mongodb.DBRef;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class ConvertingParameterAccessor implements MongoParameterAccessor {
@ -239,6 +240,14 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -239,6 +240,14 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return delegate.getValues();
}
/**
* Custom {@link Iterator} that adds a method to access elements in a converted manner.

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

@ -26,6 +26,7 @@ import org.springframework.data.repository.query.ParameterAccessor; @@ -26,6 +26,7 @@ import org.springframework.data.repository.query.ParameterAccessor;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
public interface MongoParameterAccessor extends ParameterAccessor {
@ -51,4 +52,12 @@ public interface MongoParameterAccessor extends ParameterAccessor { @@ -51,4 +52,12 @@ public interface MongoParameterAccessor extends ParameterAccessor {
* @since 1.6
*/
TextCriteria getFullText();
/**
* Returns the raw parameter values of the underlying query method.
*
* @return
* @since 1.8
*/
Object[] getValues();
}

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

@ -29,20 +29,23 @@ import org.springframework.util.ClassUtils; @@ -29,20 +29,23 @@ import org.springframework.util.ClassUtils;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class MongoParametersParameterAccessor extends ParametersParameterAccessor implements MongoParameterAccessor {
private final MongoQueryMethod method;
private final Object[] values;
/**
* Creates a new {@link MongoParametersParameterAccessor}.
*
* @param method must not be {@literal null}.
* @param values must not be {@@iteral null}.
* @param values must not be {@literal null}.
*/
public MongoParametersParameterAccessor(MongoQueryMethod method, Object[] values) {
super(method.getParameters(), values);
this.method = method;
this.values = values;
}
public Range<Distance> getDistanceRange() {
@ -121,4 +124,12 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso @@ -121,4 +124,12 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
"Expected full text parameter to be one of String, Term or TextCriteria but found %s.",
ClassUtils.getShortName(fullText.getClass())));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return values;
}
}

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

@ -22,6 +22,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -22,6 +22,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.parser.PartTree;
@ -34,6 +35,7 @@ import com.mongodb.util.JSONParseException; @@ -34,6 +35,7 @@ import com.mongodb.util.JSONParseException;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class PartTreeMongoQuery extends AbstractMongoQuery {
@ -45,11 +47,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { @@ -45,11 +47,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
* Creates a new {@link PartTreeMongoQuery} from the given {@link QueryMethod} and {@link MongoTemplate}.
*
* @param method must not be {@literal null}.
* @param template must not be {@literal null}.
* @param mongoOperations must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations) {
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
EvaluationContextProvider evaluationContextProvider) {
super(method, mongoOperations);
super(method, mongoOperations, evaluationContextProvider);
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
this.isGeoNearQuery = method.isGeoNearQuery();
this.context = mongoOperations.getConverter().getMappingContext();

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

@ -26,6 +26,11 @@ import org.slf4j.LoggerFactory; @@ -26,6 +26,11 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
@ -43,7 +48,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -43,7 +48,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
private static final ParameterBindingParser PARSER = ParameterBindingParser.INSTANCE;
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
private final String query;
private final String fieldSpec;
@ -51,15 +56,19 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -51,15 +56,19 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
private final boolean isDeleteQuery;
private final List<ParameterBinding> queryParameterBindings;
private final List<ParameterBinding> fieldSpecParameterBindings;
private final SpelExpressionParser expressionParser;
/**
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param mongoOperations must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
*/
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations) {
this(method.getAnnotatedQuery(), method, mongoOperations);
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
EvaluationContextProvider evaluationContextProvider, SpelExpressionParser expressionParser) {
this(method.getAnnotatedQuery(), method, mongoOperations, evaluationContextProvider, expressionParser);
}
/**
@ -67,17 +76,25 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -67,17 +76,25 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
* {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param template must not be {@literal null}.
*/
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations) {
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations,
EvaluationContextProvider evaluationContextProvider, SpelExpressionParser expressionParser) {
super(method, mongoOperations);
super(method, mongoOperations, evaluationContextProvider);
this.query = query;
this.queryParameterBindings = PARSER.parseParameterBindingsFrom(query);
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");
this.fieldSpec = method.getFieldSpecification();
this.fieldSpecParameterBindings = PARSER.parseParameterBindingsFrom(method.getFieldSpecification());
this.expressionParser = expressionParser;
this.queryParameterBindings = new ArrayList<ParameterBinding>();
this.query = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
this.queryParameterBindings);
this.fieldSpecParameterBindings = new ArrayList<ParameterBinding>();
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
method.getFieldSpecification(), this.fieldSpecParameterBindings);
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
@ -170,7 +187,12 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -170,7 +187,12 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
*/
private String getParameterValueForBinding(ConvertingParameterAccessor accessor, ParameterBinding binding) {
Object value = accessor.getBindableValue(binding.getParameterIndex());
Object value = null;
if (binding.isExpression()) {
value = evaluateExpression(binding.getExpression(), accessor.getValues());
} else {
value = accessor.getBindableValue(binding.getParameterIndex());
}
if (value instanceof String && binding.isQuoted()) {
return (String) value;
@ -179,6 +201,21 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -179,6 +201,21 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return JSON.serialize(value);
}
/**
* Evaluates the given {@code expressionString}.
*
* @param expressionString
* @param parameterValues
* @return
*/
private Object evaluateExpression(String expressionString, Object[] parameterValues) {
EvaluationContext evaluationContext = getEvaluationContextProvider().getEvaluationContext(
getQueryMethod().getParameters(), parameterValues);
Expression expression = expressionParser.parseExpression(expressionString);
return expression.getValue(evaluationContext, Object.class);
}
/**
* A parser that extracts the parameter bindings from a given query string.
*
@ -192,6 +229,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -192,6 +229,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
private static final String PARSEABLE_PARAMETER = "\"" + PARAMETER_PREFIX + "$1\"";
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
private static final Pattern PARSEABLE_BINDING_PATTERN = Pattern.compile("\"?" + PARAMETER_PREFIX + "(\\d+)\"?");
private static final Pattern PARAMETER_EXPRESSION_PATTERN = Pattern.compile("((:|\\?)#\\{([^}]+)\\})");
private final static int PARAMETER_INDEX_GROUP = 1;
@ -200,22 +238,53 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -200,22 +238,53 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
* {@link Collections#emptyList()}.
*
* @param input
* @param conversionService must not be {@literal null}.
* @param bindings
* @return
*/
public List<ParameterBinding> parseParameterBindingsFrom(String input) {
public String parseAndCollectParameterBindingsFromQueryIntoBindings(String input, List<ParameterBinding> bindings) {
if (!StringUtils.hasText(input)) {
return Collections.emptyList();
return input;
}
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformedInput = transformQueryAndCollectExpressionParametersIntoBindings(bindings, input);
String parseableInput = makeParameterReferencesParseable(input);
String parseableInput = makeParameterReferencesParseable(transformedInput);
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
return bindings;
return transformedInput;
}
private String transformQueryAndCollectExpressionParametersIntoBindings(List<ParameterBinding> bindings,
String input) {
Matcher matcher = PARAMETER_EXPRESSION_PATTERN.matcher(input);
StringBuilder result = new StringBuilder();
int lastPos = 0;
int exprIndex = 0;
while (matcher.find()) {
int startOffSet = matcher.start();
result.append(input.subSequence(lastPos, startOffSet));
String expression = matcher.group(3);
result.append("'?expr").append(exprIndex).append("'");
lastPos = matcher.end();
bindings.add(new ParameterBinding(exprIndex, true, expression));
exprIndex++;
}
result.append(input.subSequence(lastPos, input.length()));
return result.toString();
}
private String makeParameterReferencesParseable(String input) {
@ -292,6 +361,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -292,6 +361,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
private final int parameterIndex;
private final boolean quoted;
private final String expression;
/**
* Creates a new {@link ParameterBinding} with the given {@code parameterIndex} and {@code quoted} information.
@ -300,9 +370,14 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -300,9 +370,14 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
* @param quoted whether or not the parameter is already quoted.
*/
public ParameterBinding(int parameterIndex, boolean quoted) {
this(parameterIndex, quoted, null);
}
public ParameterBinding(int parameterIndex, boolean quoted, String expression) {
this.parameterIndex = parameterIndex;
this.quoted = quoted;
this.expression = expression;
}
public boolean isQuoted() {
@ -314,7 +389,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -314,7 +389,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
}
public String getParameter() {
return "?" + parameterIndex;
return "?" + (isExpression() ? "expr" : "") + parameterIndex;
}
public String getExpression() {
return expression;
}
public boolean isExpression() {
return this.expression != null;
}
}
}

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

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.repository.support;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;
import java.io.Serializable;
import java.lang.reflect.Method;
@ -35,18 +35,23 @@ import org.springframework.data.repository.core.NamedQueries; @@ -35,18 +35,23 @@ import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/**
* Factory to create {@link MongoRepository} instances.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class MongoRepositoryFactory extends RepositoryFactorySupport {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private final MongoOperations mongoOperations;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
@ -87,13 +92,13 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport { @@ -87,13 +92,13 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
return getTargetRepositoryViaReflection(information, entityInformation, mongoOperations);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key)
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key) {
return new MongoQueryLookupStrategy();
protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) {
return new MongoQueryLookupStrategy(evaluationContextProvider);
}
/*
@ -118,9 +123,16 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport { @@ -118,9 +123,16 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
* {@link QueryLookupStrategy} to create {@link PartTreeMongoQuery} instances.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
private class MongoQueryLookupStrategy implements QueryLookupStrategy {
private final EvaluationContextProvider evaluationContextProvider;
public MongoQueryLookupStrategy(EvaluationContextProvider evaluationContextProvider) {
this.evaluationContextProvider = evaluationContextProvider;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
@ -132,11 +144,11 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport { @@ -132,11 +144,11 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedMongoQuery(namedQuery, queryMethod, mongoOperations);
return new StringBasedMongoQuery(namedQuery, queryMethod, mongoOperations, evaluationContextProvider, EXPRESSION_PARSER);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedMongoQuery(queryMethod, mongoOperations);
return new StringBasedMongoQuery(queryMethod, mongoOperations, evaluationContextProvider, EXPRESSION_PARSER);
} else {
return new PartTreeMongoQuery(queryMethod, mongoOperations);
return new PartTreeMongoQuery(queryMethod, mongoOperations, evaluationContextProvider);
}
}
}

19
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -34,6 +34,7 @@ import org.springframework.data.geo.Point; @@ -34,6 +34,7 @@ import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.query.Param;
/**
* Sample repository managing {@link Person} entities.
@ -333,4 +334,22 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -333,4 +334,22 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
*/
@Query("{ firstname : { $in : ?0 }}")
Stream<Person> findByCustomQueryWithStreamingCursorByFirstnames(List<String> firstnames);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : ?#{[0]}}")
List<Person> findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }")
List<Person> findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : :#{#firstname}}")
List<Person> findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);
}

49
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java

@ -15,6 +15,14 @@ @@ -15,6 +15,14 @@
*/
package org.springframework.data.mongodb.repository;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.junit.Test;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
/**
@ -24,4 +32,43 @@ import org.springframework.test.context.ContextConfiguration; @@ -24,4 +32,43 @@ import org.springframework.test.context.ContextConfiguration;
* @author Thomas Darimont
*/
@ContextConfiguration
public class PersonRepositoryIntegrationTests extends AbstractPersonRepositoryIntegrationTests {}
public class PersonRepositoryIntegrationTests extends AbstractPersonRepositoryIntegrationTests {
/**
* @see DATAMONGO-990
*/
@Test
public void shouldFindByFirstnameForSpELExpressionWithParameterIndexOnly() {
List<Person> users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Dave");
assertThat(users, hasSize(1));
assertThat(users.get(0), is(dave));
}
/**
* @see DATAMONGO-990
*/
@Test
public void shouldFindByFirstnameAndCurrentUserWithCustomQuery() {
SampleSecurityContextHolder.getCurrent().setPrincipal(dave);
List<Person> users = repository.findWithSpelByFirstnameAndCurrentUserWithCustomQuery("Dave");
assertThat(users, hasSize(1));
assertThat(users.get(0), is(dave));
}
/**
* @see DATAMONGO-990
*/
@Test
public void shouldFindByFirstnameForSpELExpressionWithParameterVariableOnly() {
List<Person> users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly("Dave");
assertThat(users, hasSize(1));
assertThat(users.get(0), is(dave));
}
}

124
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SampleEvaluationContextExtension.java

@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
/*
* Copyright 2015 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
*
* http://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.mongodb.repository;
import java.util.Collections;
import java.util.Map;
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
/**
* A sample implementation of a custom {@link EvaluationContextExtension}.
*
* @author Thomas Darimont
*/
public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public Map<String, Object> getProperties() {
return Collections.singletonMap("principal", SampleSecurityContextHolder.getCurrent().getPrincipal());
}
/**
* @author Thomas Darimont
*/
public static class SampleSecurityContextHolder {
private static ThreadLocal<SampleAuthentication> auth = new ThreadLocal<SampleAuthentication>() {
protected SampleAuthentication initialValue() {
return new SampleAuthentication(new SampleUser(-1, "anonymous"));
}
};
public static SampleAuthentication getCurrent() {
return auth.get();
}
public static void clear() {
auth.remove();
}
}
/**
* @author Thomas Darimont
*/
public static class SampleAuthentication {
private Object principal;
public SampleAuthentication(Object principal) {
this.principal = principal;
}
public Object getPrincipal() {
return principal;
}
public void setPrincipal(Object principal) {
this.principal = principal;
}
}
/**
* @author Thomas Darimont
*/
public static class SampleUser {
private Object id;
private String name;
public SampleUser(Object id, String name) {
this.id = id;
this.name = name;
}
public Object getId() {
return id;
}
public void setId(Object id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SampleUser withName(String name) {
this.name = name;
return this;
}
public SampleUser withId(Object id) {
this.id = id;
return this;
}
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2015 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.
@ -15,10 +15,14 @@ @@ -15,10 +15,14 @@
*/
package org.springframework.data.mongodb.repository.query;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.Arrays;
@ -54,6 +58,7 @@ import org.springframework.data.mongodb.core.query.Query; @@ -54,6 +58,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
@ -64,6 +69,7 @@ import com.mongodb.WriteResult; @@ -64,6 +69,7 @@ import com.mongodb.WriteResult;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractMongoQueryUnitTests {
@ -314,7 +320,7 @@ public class AbstractMongoQueryUnitTests { @@ -314,7 +320,7 @@ public class AbstractMongoQueryUnitTests {
private boolean isDeleteQuery;
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
super(method, operations);
super(method, operations, DefaultEvaluationContextProvider.INSTANCE);
}
@Override

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

@ -15,10 +15,11 @@ @@ -15,10 +15,11 @@
*/
package org.springframework.data.mongodb.repository.query;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.IsTextQuery.*;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.data.mongodb.core.query.IsTextQuery.isTextQuery;
import java.lang.reflect.Method;
@ -43,6 +44,7 @@ import org.springframework.data.mongodb.repository.MongoRepository; @@ -43,6 +44,7 @@ import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.util.JSONParseException;
@ -52,6 +54,7 @@ import com.mongodb.util.JSONParseException; @@ -52,6 +54,7 @@ import com.mongodb.util.JSONParseException;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class PartTreeMongoQueryUnitTests {
@ -169,7 +172,7 @@ public class PartTreeMongoQueryUnitTests { @@ -169,7 +172,7 @@ public class PartTreeMongoQueryUnitTests {
Method method = Repo.class.getMethod(methodName, paramTypes);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContext);
return new PartTreeMongoQuery(queryMethod, mongoOperationsMock);
return new PartTreeMongoQuery(queryMethod, mongoOperationsMock, DefaultEvaluationContextProvider.INSTANCE);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (SecurityException e) {

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

@ -15,9 +15,10 @@ @@ -15,9 +15,10 @@
*/
package org.springframework.data.mongodb.repository.query;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.Collections;
@ -41,6 +42,8 @@ import org.springframework.data.mongodb.repository.Address; @@ -41,6 +42,8 @@ import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
@ -57,6 +60,8 @@ import com.mongodb.DBRef; @@ -57,6 +60,8 @@ import com.mongodb.DBRef;
@RunWith(MockitoJUnitRunner.class)
public class StringBasedMongoQueryUnitTests {
SpelExpressionParser PARSER = new SpelExpressionParser();
@Mock MongoOperations operations;
@Mock RepositoryMetadata metadata;
@Mock DbRefResolver factory;
@ -76,7 +81,8 @@ public class StringBasedMongoQueryUnitTests { @@ -76,7 +81,8 @@ public class StringBasedMongoQueryUnitTests {
Method method = SampleRepository.class.getMethod("findByLastname", String.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext());
StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, operations);
StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, operations,
DefaultEvaluationContextProvider.INSTANCE, PARSER);
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
@ -258,6 +264,21 @@ public class StringBasedMongoQueryUnitTests { @@ -258,6 +264,21 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
/**
* @see DATAMONGO-990
*/
@Test
public void shouldSupportExpressionsInCustomQueries() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
/**
* @see DATAMONGO-1070
*/
@ -293,7 +314,7 @@ public class StringBasedMongoQueryUnitTests { @@ -293,7 +314,7 @@ public class StringBasedMongoQueryUnitTests {
Method method = SampleRepository.class.getMethod(name, parameters);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext());
return new StringBasedMongoQuery(queryMethod, operations);
return new StringBasedMongoQuery(queryMethod, operations, DefaultEvaluationContextProvider.INSTANCE, PARSER);
}
private interface SampleRepository {
@ -334,5 +355,7 @@ public class StringBasedMongoQueryUnitTests { @@ -334,5 +355,7 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ ?0 : ?1}")
Object methodWithPlaceholderInKeyOfJsonStructure(String keyReplacement, String valueReplacement);
@Query(value = "{'lastname': ?#{[0]} }")
List<Person> findByQueryWithExpression(String param0);
}
}

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

@ -32,6 +32,7 @@ import org.springframework.data.repository.query.ParameterAccessor; @@ -32,6 +32,7 @@ import org.springframework.data.repository.query.ParameterAccessor;
*
* @author Oliver Gierke
* @author Christoh Strobl
* @author Thomas Darimont
*/
class StubParameterAccessor implements MongoParameterAccessor {
@ -129,4 +130,12 @@ class StubParameterAccessor implements MongoParameterAccessor { @@ -129,4 +130,12 @@ class StubParameterAccessor implements MongoParameterAccessor {
public TextCriteria getFullText() {
return null;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return this.values;
}
}

6
spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml

@ -26,6 +26,12 @@ @@ -26,6 +26,12 @@
</constructor-arg>
</bean>
</property>
<property name="evaluationContextProvider" ref="extensionAwareEvaluationContextProvider"/>
</bean>
<bean id="sampleEvaluationContextExtension" class="org.springframework.data.mongodb.repository.SampleEvaluationContextExtension"/>
<bean id="extensionAwareEvaluationContextProvider" class="org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider">
<constructor-arg ref="sampleEvaluationContextExtension"/>
</bean>
</beans>

Loading…
Cancel
Save