Skip to content

Commit

Permalink
add support for value expressions and query method evaluators in data…
Browse files Browse the repository at this point in the history
…store
  • Loading branch information
diegomarquezp committed Dec 30, 2024
1 parent e719481 commit 2afede2
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.util.Assert;
Expand All @@ -41,6 +42,9 @@ public class DatastoreQueryLookupStrategy implements QueryLookupStrategy {

private final ValueExpressionDelegate valueExpressionDelegate;

@SuppressWarnings("deprecation")
private final QueryMethodEvaluationContextProvider queryEvaluationContextProvider;

public DatastoreQueryLookupStrategy(
DatastoreMappingContext datastoreMappingContext,
DatastoreOperations datastoreOperations,
Expand All @@ -50,6 +54,19 @@ public DatastoreQueryLookupStrategy(
Assert.notNull(valueExpressionDelegate, "A non-null ValueExpressionDelegate is required.");
this.datastoreMappingContext = datastoreMappingContext;
this.valueExpressionDelegate = valueExpressionDelegate;
this.queryEvaluationContextProvider = null;
this.datastoreOperations = datastoreOperations;
}
public DatastoreQueryLookupStrategy(
DatastoreMappingContext datastoreMappingContext,
DatastoreOperations datastoreOperations,
@SuppressWarnings("deprecation") QueryMethodEvaluationContextProvider queryEvaluationContextProvider) {
Assert.notNull(datastoreMappingContext, "A non-null DatastoreMappingContext is required.");
Assert.notNull(datastoreOperations, "A non-null DatastoreOperations is required.");
Assert.notNull(queryEvaluationContextProvider, "A non-null EvaluationContextProvider is required.");
this.datastoreMappingContext = datastoreMappingContext;
this.valueExpressionDelegate = null;
this.queryEvaluationContextProvider = queryEvaluationContextProvider;
this.datastoreOperations = datastoreOperations;
}

Expand Down Expand Up @@ -80,12 +97,21 @@ public RepositoryQuery resolveQuery(

<T> GqlDatastoreQuery<T> createGqlDatastoreQuery(
Class<T> entityType, DatastoreQueryMethod queryMethod, String gql) {
if (valueExpressionDelegate != null) {
return new GqlDatastoreQuery<>(
entityType,
queryMethod,
this.datastoreOperations,
gql,
this.valueExpressionDelegate,
this.datastoreMappingContext);
}
return new GqlDatastoreQuery<>(
entityType,
queryMethod,
this.datastoreOperations,
gql,
this.valueExpressionDelegate,
this.queryEvaluationContextProvider,
this.datastoreMappingContext);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -56,6 +57,9 @@
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.SpelEvaluator;
import org.springframework.data.repository.query.SpelQueryContext;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -83,8 +87,12 @@ public class GqlDatastoreQuery<T> extends AbstractDatastoreQuery<T> {

private final ValueExpressionDelegate valueExpressionDelegate;

private final QueryMethodEvaluationContextProvider queryEvaluationContextProvider;

private ValueExpressionQueryRewriter.EvaluatingValueExpressionQueryRewriter valueExpressionQueryRewriter;

private SpelQueryContext.EvaluatingSpelQueryContext evaluatingSpelQueryContext;

/**
* Constructor.
*
Expand All @@ -104,6 +112,32 @@ public GqlDatastoreQuery(
DatastoreMappingContext datastoreMappingContext) {
super(queryMethod, datastoreTemplate, datastoreMappingContext, type);
this.valueExpressionDelegate = valueExpressionDelegate;
this.queryEvaluationContextProvider = null;
this.originalGql = StringUtils.trimTrailingCharacter(gql.trim(), ';');
setOriginalParamTags();
setEvaluatingSpelQueryContext();
setGqlResolvedEntityClassName();
}
/**
* Constructor.
*
* @param type the underlying entity type
* @param queryMethod the underlying query method to support.
* @param datastoreTemplate used for executing queries.
* @param gql the query text.
* @param evaluationContextProvider the provider used to evaluate SpEL expressions in queries.
* @param datastoreMappingContext used for getting metadata about entities.
*/
public GqlDatastoreQuery(
Class<T> type,
DatastoreQueryMethod queryMethod,
DatastoreOperations datastoreTemplate,
String gql,
QueryMethodEvaluationContextProvider evaluationContextProvider,
DatastoreMappingContext datastoreMappingContext) {
super(queryMethod, datastoreTemplate, datastoreMappingContext, type);
this.valueExpressionDelegate = null;
this.queryEvaluationContextProvider = evaluationContextProvider;
this.originalGql = StringUtils.trimTrailingCharacter(gql.trim(), ';');
setOriginalParamTags();
setEvaluatingSpelQueryContext();
Expand Down Expand Up @@ -311,19 +345,30 @@ private void setGqlResolvedEntityClassName() {
this.gqlResolvedEntityClassName = result;
}

@SuppressWarnings("deprecation")
private void setEvaluatingSpelQueryContext() {
Set<String> originalTags = new HashSet<>(GqlDatastoreQuery.this.originalParamTags);
GqlDatastoreQuery.this.valueExpressionQueryRewriter = ValueExpressionQueryRewriter.of(valueExpressionDelegate,
(Integer counter, String spelExpression) -> {
String newTag;
do {
counter++;
newTag = "@SpELtag" + counter;
} while (originalTags.contains(newTag));
originalTags.add(newTag);
return newTag;
}, (left, right) -> right)
.withEvaluationContextAccessor(valueExpressionDelegate.getEvaluationContextAccessor());
BiFunction<Integer, String, String> parameterNameSource = (Integer counter, String spelExpression) -> {
String newTag;
do {
counter++;
newTag = "@SpELtag" + counter;
} while (originalTags.contains(newTag));
originalTags.add(newTag);
return newTag;
};
// We favor ValueExpressionDelegate since it's not deprecated
if (valueExpressionDelegate != null) {
GqlDatastoreQuery.this.valueExpressionQueryRewriter = ValueExpressionQueryRewriter.of(valueExpressionDelegate,
parameterNameSource, (left, right) -> right)
.withEvaluationContextAccessor(valueExpressionDelegate.getEvaluationContextAccessor());
} else {
GqlDatastoreQuery.this.evaluatingSpelQueryContext =
SpelQueryContext.of(
parameterNameSource,
(prefix, newTag) -> newTag)
.withEvaluationContextProvider(GqlDatastoreQuery.this.queryEvaluationContextProvider);
}
}

// Convenience class to hold a grouping of GQL, tags, and parameter values.
Expand All @@ -348,22 +393,42 @@ private class ParsedQueryWithTagsAndValues {

int limitPosition;

Map<String, Object> evaluationResults;

/**
* This method prepares the Gql query and its evaluation results. It will favor
* {@link ValueExpressionDelegate} over the deprecated
* {@link QueryMethodEvaluationContextProvider}.
*/
@SuppressWarnings("deprecation")
private void evaluateGql() {
if (GqlDatastoreQuery.this.valueExpressionDelegate != null) {
ValueExpressionQueryRewriter.QueryExpressionEvaluator spelEvaluator =
GqlDatastoreQuery.this.valueExpressionQueryRewriter.parse(
GqlDatastoreQuery.this.gqlResolvedEntityClassName,
GqlDatastoreQuery.this.queryMethod.getParameters());
this.evaluationResults = spelEvaluator.evaluate(this.rawParams);
this.finalGql = spelEvaluator.getQueryString();
} else {
SpelEvaluator spelEvaluator =
GqlDatastoreQuery.this.evaluatingSpelQueryContext.parse(
GqlDatastoreQuery.this.gqlResolvedEntityClassName,
GqlDatastoreQuery.this.queryMethod.getParameters());
this.evaluationResults = spelEvaluator.evaluate(this.rawParams);
this.finalGql = spelEvaluator.getQueryString();
}
}

ParsedQueryWithTagsAndValues(List<String> initialTags, Object[] rawParams) {
this.params =
Arrays.stream(rawParams)
.filter(e -> !(e instanceof Pageable || e instanceof Sort))
.collect(Collectors.toList());
this.rawParams = rawParams;
this.tagsOrdered = new ArrayList<>(initialTags);
evaluateGql();

ValueExpressionQueryRewriter.QueryExpressionEvaluator spelEvaluator =
GqlDatastoreQuery.this.valueExpressionQueryRewriter.parse(
GqlDatastoreQuery.this.gqlResolvedEntityClassName,
GqlDatastoreQuery.this.queryMethod.getParameters());
Map<String, Object> results = spelEvaluator.evaluate(this.rawParams);
this.finalGql = spelEvaluator.getQueryString();

for (Map.Entry<String, Object> entry : results.entrySet()) {
for (Map.Entry<String, Object> entry : this.evaluationResults.entrySet()) {
this.params.add(entry.getValue());
// Cloud Datastore requires the tag name without the @
this.tagsOrdered.add(entry.getKey().substring(1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,22 @@
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.repository.core.EntityInformation;
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.Parameters;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Expand Down Expand Up @@ -101,8 +109,65 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(
valueExpressionDelegate));
}

/**
* @deprecated in favor of {@link #getQueryLookupStrategy(Key, ValueExpressionDelegate)}
*/
@Override
@SuppressWarnings("deprecation")
@Deprecated(since = "6.0")
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(
@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) {

return Optional.of(
new DatastoreQueryLookupStrategy(
this.datastoreMappingContext,
this.datastoreOperations,
delegateContextProvider(evaluationContextProvider)));

}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@SuppressWarnings("deprecation")
private QueryMethodEvaluationContextProvider delegateContextProvider(
QueryMethodEvaluationContextProvider evaluationContextProvider) {

return new QueryMethodEvaluationContextProvider() {
@Override
public <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(
T parameters, Object[] parameterValues) {
StandardEvaluationContext evaluationContext =
(StandardEvaluationContext)
evaluationContextProvider.getEvaluationContext(parameters, parameterValues);
evaluationContext.setRootObject(DatastoreRepositoryFactory.this.applicationContext);
evaluationContext.addPropertyAccessor(new BeanFactoryAccessor());
evaluationContext.setBeanResolver(
new BeanFactoryResolver(DatastoreRepositoryFactory.this.applicationContext));
return evaluationContext;
}

@Override
public <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(
T parameters, Object[] parameterValues, ExpressionDependencies expressionDependencies) {
StandardEvaluationContext evaluationContext =
(StandardEvaluationContext)
evaluationContextProvider.getEvaluationContext(
parameters, parameterValues, expressionDependencies);

evaluationContext.setRootObject(DatastoreRepositoryFactory.this.applicationContext);
evaluationContext.addPropertyAccessor(new BeanFactoryAccessor());
evaluationContext.setBeanResolver(
new BeanFactoryResolver(DatastoreRepositoryFactory.this.applicationContext));
return evaluationContext;
}

@Override
public EvaluationContextProvider getEvaluationContextProvider() {
return (EvaluationContextProvider) evaluationContextProvider;
}
};
}
}

0 comments on commit 2afede2

Please sign in to comment.