DATAKV-115 - Make derived finder execution thread-safe.

We now make sure separate SpelExpression and EvaluationContext for each and every derived finder execution to assert the expression context cannot be accidentally overridden.

Original pull request: #16.
This commit is contained in:
Christoph Strobl
2015-11-02 13:26:24 +01:00
committed by Oliver Gierke
parent 3c52aff26c
commit e22ee22e58
6 changed files with 175 additions and 37 deletions

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2016 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.keyvalue.core;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* {@link SpelCriteria} allows to pass on a {@link SpelExpression} and {@link EvaluationContext} to the actual query
* processor. This decouples the {@link SpelExpression} from the context it is used in.
*
* @author Christoph Strobl
*/
public class SpelCriteria {
private final SpelExpression expression;
private final EvaluationContext context;
/**
* Creates new {@link SpelCriteria}.
*
* @param expression must not be {@literal null}.
* @param context can be {@literal null} and will be defaulted to {@link StandardEvaluationContext}.
*/
public SpelCriteria(SpelExpression expression, EvaluationContext context) {
this.expression = expression;
this.context = context == null ? new StandardEvaluationContext() : context;
}
/**
* @return never {@literal null}.
*/
public EvaluationContext getContext() {
return context;
}
/**
* @return never {@literal null}.
*/
public SpelExpression getExpression() {
return expression;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -18,6 +18,7 @@ package org.springframework.data.keyvalue.core;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/**
@@ -26,7 +27,7 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Oliver Gierke
*/
class SpelCriteriaAccessor implements CriteriaAccessor<SpelExpression> {
class SpelCriteriaAccessor implements CriteriaAccessor<SpelCriteria> {
private final SpelExpressionParser parser;
@@ -40,20 +41,25 @@ class SpelCriteriaAccessor implements CriteriaAccessor<SpelExpression> {
}
@Override
public SpelExpression resolve(KeyValueQuery<?> query) {
public SpelCriteria resolve(KeyValueQuery<?> query) {
if (query.getCritieria() == null) {
return null;
}
if (query.getCritieria() instanceof SpelExpression) {
return (SpelExpression) query.getCritieria();
return new SpelCriteria((SpelExpression) query.getCritieria(), new StandardEvaluationContext());
}
if (query.getCritieria() instanceof String) {
return parser.parseRaw((String) query.getCritieria());
return new SpelCriteria(parser.parseRaw((String) query.getCritieria()), new StandardEvaluationContext());
}
if (query.getCritieria() instanceof SpelCriteria) {
return (SpelCriteria) query.getCritieria();
}
throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCritieria());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -35,7 +35,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* @author Oliver Gierke
* @param <T>
*/
class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAdapter, SpelExpression, Comparator<?>> {
class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAdapter, SpelCriteria, Comparator<?>> {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
@@ -51,7 +51,7 @@ class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAda
* @see org.springframework.data.keyvalue.core.QueryEngine#execute(java.lang.Object, java.lang.Object, int, int, java.io.Serializable)
*/
@Override
public Collection<?> execute(SpelExpression criteria, Comparator<?> sort, int offset, int rows, Serializable keyspace) {
public Collection<?> execute(SpelCriteria criteria, Comparator<?> sort, int offset, int rows, Serializable keyspace) {
return sortAndFilterMatchingRange(getAdapter().getAllOf(keyspace), criteria, sort, offset, rows);
}
@@ -60,12 +60,12 @@ class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAda
* @see org.springframework.data.keyvalue.core.QueryEngine#count(java.lang.Object, java.io.Serializable)
*/
@Override
public long count(SpelExpression criteria, Serializable keyspace) {
public long count(SpelCriteria criteria, Serializable keyspace) {
return filterMatchingRange(getAdapter().getAllOf(keyspace), criteria, -1, -1).size();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<?> sortAndFilterMatchingRange(Iterable<?> source, SpelExpression criteria, Comparator sort, int offset,
private List<?> sortAndFilterMatchingRange(Iterable<?> source, SpelCriteria criteria, Comparator sort, int offset,
int rows) {
List<?> tmp = IterableConverter.toList(source);
@@ -76,7 +76,7 @@ class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAda
return filterMatchingRange(tmp, criteria, offset, rows);
}
private static <S> List<S> filterMatchingRange(Iterable<S> source, SpelExpression criteria, int offset, int rows) {
private static <S> List<S> filterMatchingRange(Iterable<S> source, SpelCriteria criteria, int offset, int rows) {
List<S> result = new ArrayList<S>();
@@ -90,10 +90,11 @@ class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAda
if (!matches) {
try {
matches = criteria.getValue(candidate, Boolean.class);
matches = criteria.getExpression().getValue(criteria.getContext(), candidate, Boolean.class);
} catch (SpelEvaluationException e) {
criteria.getEvaluationContext().setVariable("it", candidate);
matches = criteria.getValue() == null ? false : criteria.getValue(Boolean.class);
criteria.getContext().setVariable("it", candidate);
matches = criteria.getExpression().getValue(criteria.getContext()) == null ? false : criteria.getExpression()
.getValue(criteria.getContext(), Boolean.class);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -23,6 +23,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.IterableConverter;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.SpelCriteria;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ParameterAccessor;
@@ -93,7 +94,7 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
public Object execute(Object[] parameters) {
ParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), parameters);
KeyValueQuery<?> query = prepareQuery(parameters, accessor);
KeyValueQuery<?> query = prepareQuery(parameters);
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
return processor.processResult(doExecute(parameters, query));
@@ -114,8 +115,8 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
Iterable<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
long count = queryMethod.isSliceQuery() ? 0
: keyValueOperations.count(query, queryMethod.getEntityInformation().getJavaType());
long count = queryMethod.isSliceQuery() ? 0 : keyValueOperations.count(query, queryMethod.getEntityInformation()
.getJavaType());
return new PageImpl(IterableConverter.toList(result), page, count);
@@ -133,7 +134,9 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private KeyValueQuery<?> prepareQuery(Object[] parameters, ParameterAccessor accessor) {
protected KeyValueQuery<?> prepareQuery(Object[] parameters) {
ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), parameters);
if (this.query == null) {
this.query = createQuery(accessor);
@@ -141,6 +144,14 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
KeyValueQuery<?> q = new KeyValueQuery(this.query.getCritieria());
if (this.query.getCritieria() instanceof SpelExpression) {
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(),
parameters);
SpelCriteria spelCriteria = new SpelCriteria((SpelExpression) this.query.getCritieria(), context);
q = new KeyValueQuery(spelCriteria);
}
if (accessor.getPageable() != null) {
q.setOffset(accessor.getPageable().getOffset());
q.setRows(accessor.getPageable().getPageSize());
@@ -153,12 +164,6 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
q.setSort(sort != null ? sort : query.getSort());
if (q.getCritieria() instanceof SpelExpression) {
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(),
parameters);
((SpelExpression) q.getCritieria()).setEvaluationContext(context);
}
return q;
}