diff --git a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java index 756638b66..249c7fc62 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java @@ -23,6 +23,7 @@ import javax.persistence.Query; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; @@ -33,7 +34,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Thomas Darimont */ -class CriteriaQueryParameterBinder extends ParameterBinder { +class CriteriaQueryParameterBinder extends ExpressionAwareParameterBinder { private final Iterator> expressions; @@ -42,10 +43,14 @@ class CriteriaQueryParameterBinder extends ParameterBinder { * {@link javax.persistence.criteria.ParameterExpression}. * * @param parameters + * @param values + * @param expressions + * @param evaluationContextProvider */ - CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable> expressions) { + CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable> expressions, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values); + super(parameters, values, evaluationContextProvider); Assert.notNull(expressions); this.expressions = expressions.iterator(); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java new file mode 100644 index 000000000..24bd60a44 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java @@ -0,0 +1,240 @@ +/* + * Copyright 2014 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.jpa.repository.query; + +import java.util.List; + +import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ConstructorResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.Assert; + +/** + * A {@link ParameterBinder} that is able to detect and dynamically evaluate SpEL expression based parameters. + * + * @author Thomas Darimont + */ +class ExpressionAwareParameterBinder extends ParameterBinder { + + private final ExpressionEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@literal ExpressionAwareParameterBinder}. + * + * @param parameters + * @param evaluationContextProvider + */ + public ExpressionAwareParameterBinder(JpaParameters parameters, + ExpressionEvaluationContextProvider evaluationContextProvider) { + this(parameters, new Object[0], evaluationContextProvider); + } + + /** + * Creates a new {@literal ExpressionAwareParameterBinder}. + * + * @param parameters + * @param values + * @param evaluationContextProvider + */ + public ExpressionAwareParameterBinder(JpaParameters parameters, Object[] values, + ExpressionEvaluationContextProvider evaluationContextProvider) { + + super(parameters, values); + + Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!"); + + this.evaluationContextProvider = evaluationContextProvider; + } + + /* (non-Javadoc) + * @see org.springframework.data.jpa.repository.query.ParameterBinder#computeParameterValue(org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter, java.lang.Object, java.lang.Object[]) + */ + @Override + protected Object computeParameterValue(JpaParameter parameter, Object value, Object[] values) { + + if (!parameter.isExpressionParameter()) { + return super.computeParameterValue(parameter, value, values); + } + + if (value instanceof String) { + return evaluateExpression((String) value); + } + + if (value instanceof Expression) { + return evaluateExpression((Expression) value); + } + + throw new IllegalArgumentException("Cannot convert Value: " + value + " to a SpEL expression"); + } + + /** + * Parses the given {@code expressionString} into a SpEL {@link Expression}. + * + * @param expressionString + * @return + */ + protected Expression parseExpressionString(String expressionString) { + return new SpelExpressionParser().parseExpression(expressionString); + } + + /** + * Evaluates the given {@code expressionString} as a SpEL {@link Expression}. + * + * @param expressionString + * @return + */ + protected Object evaluateExpression(String expressionString) { + return evaluateExpression(parseExpressionString(expressionString)); + } + + /** + * Evaluates the given SpEL {@link Expression}. + * + * @param expr + * @return + */ + protected Object evaluateExpression(Expression expr) { + return expr.getValue(getEvaluationContext(), String.class); + } + + /** + * Returns the {@link StandardEvaluationContext} to use for evaluation. + * + * @return + */ + protected StandardEvaluationContext getEvaluationContext() { + + EvaluationContext delegatee = evaluationContextProvider.getEvaluationContext(); + StandardEvaluationContext evalContext = new DelegatingStandardEvaluationContext(getValues(), delegatee); + + return evalContext; + } + + /** + * A {@link StandardEvaluationContext} that delegates to the given {@link EvaluationContext}. Variables are first + * looked-up locally and if not the lookup is performed against the delegatee. + * + * @author Thomas Darimont + */ + protected static class DelegatingStandardEvaluationContext extends StandardEvaluationContext { + + private final EvaluationContext delegatee; + + /** + * Creates a new {@link DelegatingStandardEvaluationContext}. + * + * @param values must not be {@literal null} + * @param delegatee must not be {@literal null} + */ + public DelegatingStandardEvaluationContext(Object[] values, EvaluationContext delegatee) { + + super(values); + + Assert.notNull(delegatee, "EvaluationContext delegatee must not be null!"); + + this.delegatee = delegatee; + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getConstructorResolvers() + */ + @Override + public List getConstructorResolvers() { + return delegatee.getConstructorResolvers(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers() + */ + @Override + public List getMethodResolvers() { + return delegatee.getMethodResolvers(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors() + */ + @Override + public List getPropertyAccessors() { + return delegatee.getPropertyAccessors(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeLocator() + */ + @Override + public TypeLocator getTypeLocator() { + return delegatee.getTypeLocator(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeConverter() + */ + @Override + public TypeConverter getTypeConverter() { + return delegatee.getTypeConverter(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeComparator() + */ + @Override + public TypeComparator getTypeComparator() { + return delegatee.getTypeComparator(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getOperatorOverloader() + */ + @Override + public OperatorOverloader getOperatorOverloader() { + return delegatee.getOperatorOverloader(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getBeanResolver() + */ + @Override + public BeanResolver getBeanResolver() { + return delegatee.getBeanResolver(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#lookupVariable(java.lang.String) + */ + @Override + public Object lookupVariable(String name) { + + Object result = super.lookupVariable(name); + if (result != null) { + return result; + } + + return delegatee.lookupVariable(name); + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index fc3915bf6..868b3f484 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -26,6 +26,7 @@ import org.springframework.data.jpa.repository.Temporal; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; +import org.springframework.expression.Expression; /** * Custom extension of {@link Parameters} discovering additional query parameter annotations. @@ -110,6 +111,13 @@ public class JpaParameters extends Parameters { return isDateParameter() && hasTemporalParamAnnotation(); } + /** + * @return {@literal true} if this parameter is of type {@link Expression}. + */ + public boolean isExpressionParameter() { + return Expression.class.equals(getType()); + } + /** * @return the {@link TemporalType} on the {@link Temporal} annotation of the given {@link Parameter}. */ diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index ceab37b15..8ea71d398 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -50,11 +50,14 @@ public final class JpaQueryLookupStrategy { private final EntityManager em; private final QueryExtractor provider; + protected final ExpressionEvaluationContextProvider evaluationContextProvider; - public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor) { + public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor, + ExpressionEvaluationContextProvider evaluationContextProvider) { this.em = em; this.provider = extractor; + this.evaluationContextProvider = evaluationContextProvider; } /* @@ -80,16 +83,17 @@ public final class JpaQueryLookupStrategy { */ private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy { - public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor) { + public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(em, extractor); + super(em, extractor, evaluationContextProvider); } @Override protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) { try { - return new PartTreeJpaQuery(method, em); + return new PartTreeJpaQuery(method, em, this.evaluationContextProvider); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("Could not create query metamodel for method %s!", method.toString()), e); @@ -105,13 +109,10 @@ public final class JpaQueryLookupStrategy { */ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy { - private final ExpressionEvaluationContextProvider evaluationContextProvider; - public DeclaredQueryLookupStrategy(EntityManager em, QueryExtractor extractor, ExpressionEvaluationContextProvider evaluationContextProvider) { - super(em, extractor); - this.evaluationContextProvider = evaluationContextProvider; + super(em, extractor, evaluationContextProvider); } @Override @@ -159,9 +160,11 @@ public final class JpaQueryLookupStrategy { private final CreateQueryLookupStrategy createStrategy; public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor, - CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) { + CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy, + ExpressionEvaluationContextProvider evaluationContextProvider) { + + super(em, extractor, evaluationContextProvider); - super(em, extractor); this.createStrategy = createStrategy; this.lookupStrategy = lookupStrategy; } @@ -190,12 +193,13 @@ public final class JpaQueryLookupStrategy { switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) { case CREATE: - return new CreateQueryLookupStrategy(em, extractor); + return new CreateQueryLookupStrategy(em, extractor, evaluationContextProvider); case USE_DECLARED_QUERY: return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider); case CREATE_IF_NOT_FOUND: - return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor), - new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider)); + return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor, + evaluationContextProvider), new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider), + evaluationContextProvider); default: throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index bc4448b50..f63b7a895 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -106,7 +106,8 @@ public class ParameterBinder { if (parameter.isBindable()) { - Object value = values[methodParameterPosition]; + Object value = computeParameterValue(parameter, values[methodParameterPosition], values); + bind(query, parameter, value, queryParameterPosition++); } @@ -116,6 +117,27 @@ public class ParameterBinder { return query; } + /** + * Computes the value to bind for the given {@link JpaParameter} and the given {@code value} by potentially using the + * other {@code values}. This is intended to be customized in sub-classes. + * + * @param parameter + * @param value + * @param values + * @return + */ + protected Object computeParameterValue(JpaParameter parameter, Object value, Object[] values) { + return value; + } + + /** + * Perform the actual query parameter binding. + * + * @param query + * @param parameter + * @param value + * @param position + */ protected void bind(Query query, JpaParameter parameter, Object value, int position) { if (parameter.isTemporalParameter()) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index 4457acdee..aa191a961 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -30,6 +30,7 @@ import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.expression.Expression; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -121,7 +122,14 @@ class ParameterMetadataProvider { Assert.notNull(type); - ParameterExpression expression = name == null ? builder.parameter(type) : builder.parameter(type, name); + /* + * We treat Expression types as Object vales since the real value to be bound as a parameter is determined at query time. + */ + @SuppressWarnings("unchecked") + Class reifiedType = Expression.class.equals(type) ? (Class) Object.class : type; + + ParameterExpression expression = name == null ? builder.parameter(reifiedType) : builder.parameter(reifiedType, + name); ParameterMetadata value = new ParameterMetadata(expression, part.getType(), accessor == null ? ParameterMetadata.PLACEHOLDER : accessor.next()); expressions.add(value); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 0029903ac..2236c79d4 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -26,6 +26,8 @@ import javax.persistence.criteria.CriteriaQuery; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; +import org.springframework.data.jpa.repository.support.StandardExpressionEvaluationContextProvider; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; @@ -43,7 +45,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { private final QueryPreparer query; private final QueryPreparer countQuery; - private EntityManager em; + private final EntityManager em; + private final ExpressionEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link PartTreeJpaQuery}. @@ -53,8 +56,22 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { */ public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) { + this(method, em, StandardExpressionEvaluationContextProvider.INSTANCE); + } + + /** + * Creates a new {@link PartTreeJpaQuery}. + * + * @param method must not be {@literal null}. + * @param em must not be {@literal null}. + * @param evaluationContextProvider + */ + public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, + ExpressionEvaluationContextProvider evaluationContextProvider) { + super(method, em); this.em = em; + this.evaluationContextProvider = evaluationContextProvider; this.domainClass = method.getEntityInformation().getJavaType(); this.tree = new PartTree(method.getName(), domainClass); @@ -205,7 +222,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { } private ParameterBinder getBinder(Object[] values, List> expressions) { - return new CriteriaQueryParameterBinder(parameters, values, expressions); + return new CriteriaQueryParameterBinder(parameters, values, expressions, evaluationContextProvider); } private Sort getDynamicSort(Object[] values) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java index 6123b3fd0..e4183044a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java @@ -15,24 +15,11 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; - import javax.persistence.Query; import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; -import org.springframework.expression.BeanResolver; -import org.springframework.expression.ConstructorResolver; -import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; -import org.springframework.expression.MethodResolver; -import org.springframework.expression.OperatorOverloader; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeComparator; -import org.springframework.expression.TypeConverter; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** @@ -43,7 +30,6 @@ import org.springframework.util.Assert; class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinder { private final StringQuery query; - private final ExpressionEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link SpelExpressionStringQueryParameterBinder}. @@ -56,12 +42,11 @@ class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinde public SpelExpressionStringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query, ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values, query); + super(parameters, values, query, evaluationContextProvider); Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!"); this.query = query; - this.evaluationContextProvider = evaluationContextProvider; } /* (non-Javadoc) @@ -82,124 +67,18 @@ class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinde if (binding.isExpression()) { - Expression expr = new SpelExpressionParser().parseExpression(binding.getExpression()); + Expression expr = parseExpressionString(binding.getExpression()); - EvaluationContext delegatee = evaluationContextProvider.getEvaluationContext(); - StandardEvaluationContext evalContext = new DelegatingStandardEvaluationContext(getValues(), delegatee); - Object actualValue = expr.getValue(evalContext, String.class); + Object value = evaluateExpression(expr); if (binding.getName() != null) { - jpaQuery.setParameter(binding.getName(), binding.prepare(actualValue)); + jpaQuery.setParameter(binding.getName(), binding.prepare(value)); } else { - jpaQuery.setParameter(binding.getPosition(), binding.prepare(actualValue)); + jpaQuery.setParameter(binding.getPosition(), binding.prepare(value)); } } } return jpaQuery; } - - /** - * A {@link StandardEvaluationContext} that delegates to the given {@link EvaluationContext}. Variables are first - * looked-up locally and if not the lookup is performed against the delegatee. - * - * @author Thomas Darimont - */ - static class DelegatingStandardEvaluationContext extends StandardEvaluationContext { - - private final EvaluationContext delegatee; - - /** - * Creates a new {@link DelegatingStandardEvaluationContext}. - * - * @param values must not be {@literal null} - * @param delegatee must not be {@literal null} - */ - public DelegatingStandardEvaluationContext(Object[] values, EvaluationContext delegatee) { - - super(values); - - Assert.notNull(delegatee, "EvaluationContext delegatee must not be null!"); - - this.delegatee = delegatee; - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getConstructorResolvers() - */ - @Override - public List getConstructorResolvers() { - return delegatee.getConstructorResolvers(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers() - */ - @Override - public List getMethodResolvers() { - return delegatee.getMethodResolvers(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors() - */ - @Override - public List getPropertyAccessors() { - return delegatee.getPropertyAccessors(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeLocator() - */ - @Override - public TypeLocator getTypeLocator() { - return delegatee.getTypeLocator(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeConverter() - */ - @Override - public TypeConverter getTypeConverter() { - return delegatee.getTypeConverter(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeComparator() - */ - @Override - public TypeComparator getTypeComparator() { - return delegatee.getTypeComparator(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getOperatorOverloader() - */ - @Override - public OperatorOverloader getOperatorOverloader() { - return delegatee.getOperatorOverloader(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getBeanResolver() - */ - @Override - public BeanResolver getBeanResolver() { - return delegatee.getBeanResolver(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#lookupVariable(java.lang.String) - */ - @Override - public Object lookupVariable(String name) { - - Object result = super.lookupVariable(name); - if (result != null) { - return result; - } - - return delegatee.lookupVariable(name); - } - } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java index b792d311a..6f0e164d5 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java @@ -20,6 +20,7 @@ import javax.persistence.Query; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding; import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; @@ -30,7 +31,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Thomas Darimont */ -public class StringQueryParameterBinder extends ParameterBinder { +public class StringQueryParameterBinder extends ExpressionAwareParameterBinder { private final StringQuery query; @@ -41,10 +42,12 @@ public class StringQueryParameterBinder extends ParameterBinder { * @param parameters must not be {@literal null}. * @param values must not be {@literal null}. * @param query must not be {@literal null}. + * @param evaluationContextProvider */ - public StringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query) { + public StringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values); + super(parameters, values, evaluationContextProvider); Assert.notNull(query, "StringQuery must not be null!"); this.query = query; diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 97edc3351..9e7f8e1f3 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -59,6 +59,8 @@ import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -287,7 +289,7 @@ public class UserRepositoryTests { flushTestUsers(); - repository.findByLastname(null); + repository.findByLastname((String) null); } @Test @@ -1615,6 +1617,21 @@ public class UserRepositoryTests { assertThat(users.get(0), is(firstUser)); } + /** + * @see DATAJPA-XXX + */ + @Test + public void shouldFindUserByLastnameWithSpelExpressionInDerivedQuery() { + + flushTestUsers(); + + Expression expr = new SpelExpressionParser().parseExpression("'Gier' + 'ke'"); + List users = repository.queryByLastname(expr); + + assertThat(users, hasSize(1)); + assertThat(users.get(0), is(firstUser)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index b64aa5a44..a6b19a50b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -37,6 +37,7 @@ import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.query.Procedure; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.springframework.expression.Expression; import org.springframework.transaction.annotation.Transactional; import com.google.common.base.Optional; @@ -471,4 +472,9 @@ public interface UserRepository extends JpaRepository, JpaSpecifi */ @Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%") List findByLastnameWithSpelExpression(@Param("lastname") String lastname); + + /** + * @see DATAJPA-XXX + */ + List queryByLastname(Expression lastname); }