DATACMNS-1108 - Add support for Reactive SpEL Context retrieval.
We now support reactive EvaluationContext retrieval in conjunction with ReactiveEvaluationContextExtensions. Reactive SpEL Context extensions may access Reactor's Context and provide contextual details during SpEL evaluation. Original Pull Request: #454
This commit is contained in:
committed by
Christoph Strobl
parent
8a9033fdd5
commit
1c354f6d4e
@@ -54,6 +54,7 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
class EvaluationContextExtensionInformation {
|
||||
@@ -100,6 +101,28 @@ class EvaluationContextExtensionInformation {
|
||||
.orElse(RootObjectInformation.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this extension provides {@link ExpressionDependencies.ExpressionDependency}.
|
||||
*
|
||||
* @param dependency the expression dependency.
|
||||
* @return {@literal true} if {@link ExpressionDependencies.ExpressionDependency} is provided by this root object or
|
||||
* the extension itself.
|
||||
* @since 2.4
|
||||
*/
|
||||
public boolean provides(ExpressionDependencies.ExpressionDependency dependency) {
|
||||
|
||||
// We don't know, extension declares Object getRootObject
|
||||
if (!rootObjectInformation.isPresent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rootObjectInformation.filter(it -> it.provides(dependency)).isPresent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return extensionTypeInformation.provides(dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static information about the given {@link EvaluationContextExtension} type. Discovers public static methods and
|
||||
* fields. The fields' values are obtained directly, the methods are exposed {@link Function} invocations.
|
||||
@@ -135,6 +158,26 @@ class EvaluationContextExtensionInformation {
|
||||
this.properties = discoverDeclaredProperties(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this extension provides {@link ExpressionDependencies.ExpressionDependency}.
|
||||
*
|
||||
* @param dependency the expression dependency.
|
||||
* @return {@literal true} if {@link ExpressionDependencies.ExpressionDependency} is provided by this root object.
|
||||
* @since 2.4
|
||||
*/
|
||||
public boolean provides(ExpressionDependencies.ExpressionDependency dependency) {
|
||||
|
||||
if (dependency.isPropertyOrField()) {
|
||||
return properties.containsKey(dependency.getSymbol());
|
||||
}
|
||||
|
||||
if (dependency.isMethod()) {
|
||||
return functions.containsKey(dependency.getSymbol());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, Function> discoverDeclaredFunctions(Class<?> type) {
|
||||
|
||||
MultiValueMap<String, Function> map = CollectionUtils.toMultiValueMap(new HashMap<>());
|
||||
@@ -201,6 +244,7 @@ class EvaluationContextExtensionInformation {
|
||||
* Information about the root object of an extension.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class RootObjectInformation {
|
||||
|
||||
@@ -277,6 +321,43 @@ class EvaluationContextExtensionInformation {
|
||||
|
||||
}).orElseGet(Collections::emptyMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this root object information provides {@link ExpressionDependencies.ExpressionDependency}.
|
||||
*
|
||||
* @param dependency the expression dependency.
|
||||
* @return {@literal true} if {@link ExpressionDependencies.ExpressionDependency} is provided by this root object.
|
||||
*/
|
||||
public boolean provides(ExpressionDependencies.ExpressionDependency dependency) {
|
||||
|
||||
if (dependency.isPropertyOrField()) {
|
||||
|
||||
if (accessors.containsKey(dependency.getSymbol())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Field field : fields) {
|
||||
if (field.getName().equals(dependency.getSymbol())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dependency.isMethod()) {
|
||||
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(dependency.getSymbol())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> discoverDeclaredProperties(Class<?> type) {
|
||||
|
||||
@@ -24,8 +24,10 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EvaluationContextProvider {
|
||||
|
||||
/**
|
||||
@@ -43,4 +45,19 @@ public interface EvaluationContextProvider {
|
||||
* @return
|
||||
*/
|
||||
EvaluationContext getEvaluationContext(Object rootObject);
|
||||
|
||||
/**
|
||||
* Returns a tailored {@link EvaluationContext} built using the given parameter values and considering
|
||||
* {@link ExpressionDependencies.ExpressionDependency expression dependencies}. The returned {@link EvaluationContext}
|
||||
* may contain a reduced visibility of methods and properties/fields according to the required
|
||||
* {@link ExpressionDependencies.ExpressionDependency expression dependencies}.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @param dependencies the requested expression dependencies to be available.
|
||||
* @return
|
||||
* @since 2.4
|
||||
*/
|
||||
default EvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
return getEvaluationContext(rootObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.spel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
import org.springframework.expression.spel.ast.CompoundExpression;
|
||||
import org.springframework.expression.spel.ast.MethodReference;
|
||||
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Value object capturing dependencies to a method or property/field that is referenced from a SpEL expression.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
*/
|
||||
public class ExpressionDependencies implements Streamable<ExpressionDependencies.ExpressionDependency> {
|
||||
|
||||
private static final ExpressionDependencies EMPTY = new ExpressionDependencies(Collections.emptyList());
|
||||
|
||||
private final List<ExpressionDependency> dependencies;
|
||||
|
||||
private ExpressionDependencies(List<ExpressionDependency> dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover all expression dependencies that are referenced in the {@link SpelNode expression root}.
|
||||
*
|
||||
* @param expression the SpEL expression to inspect.
|
||||
* @return a set of {@link ExpressionDependencies}.
|
||||
*/
|
||||
public static ExpressionDependencies discover(Expression expression) {
|
||||
return expression instanceof SpelExpression ? discover(((SpelExpression) expression).getAST(), true) : EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover all expression dependencies that are referenced in the {@link SpelNode expression root}.
|
||||
*
|
||||
* @param root the SpEL expression to inspect.
|
||||
* @param topLevelOnly whether to include top-level dependencies only. Top-level dependencies are dependencies that
|
||||
* indicate the start of a compound expression and required to resolve the next expression item.
|
||||
* @return a set of {@link ExpressionDependencies}.
|
||||
*/
|
||||
public static ExpressionDependencies discover(SpelNode root, boolean topLevelOnly) {
|
||||
|
||||
List<ExpressionDependency> dependencies = new ArrayList<>();
|
||||
|
||||
collectDependencies(root, 0, expressionDependency -> {
|
||||
if (!topLevelOnly || expressionDependency.isTopLevel()) {
|
||||
dependencies.add(expressionDependency);
|
||||
}
|
||||
});
|
||||
|
||||
return new ExpressionDependencies(Collections.unmodifiableList(dependencies));
|
||||
}
|
||||
|
||||
private static void collectDependencies(SpelNode node, int compoundPosition,
|
||||
Consumer<ExpressionDependency> dependencies) {
|
||||
|
||||
if (node instanceof MethodReference) {
|
||||
dependencies.accept(ExpressionDependency.forMethod(((MethodReference) node).getName()).nest(compoundPosition));
|
||||
}
|
||||
|
||||
if (node instanceof PropertyOrFieldReference) {
|
||||
dependencies.accept(
|
||||
ExpressionDependency.forPropertyOrField(((PropertyOrFieldReference) node).getName()).nest(compoundPosition));
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
collectDependencies(node.getChild(i), node instanceof CompoundExpression ? i : 0, dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<ExpressionDependency> iterator() {
|
||||
return this.dependencies.iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ExpressionDependencies)) {
|
||||
return false;
|
||||
}
|
||||
ExpressionDependencies that = (ExpressionDependencies) o;
|
||||
return ObjectUtils.nullSafeEquals(dependencies, that.dependencies);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to describe a dependency to a method or property/field that is referenced from a SpEL expression.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
*/
|
||||
public static class ExpressionDependency {
|
||||
|
||||
private final DependencyType type;
|
||||
private final String symbol;
|
||||
private final int nestLevel;
|
||||
|
||||
private ExpressionDependency(DependencyType type, String symbol, int nestLevel) {
|
||||
this.symbol = symbol;
|
||||
this.nestLevel = nestLevel;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpressionDependency} for a method.
|
||||
*
|
||||
* @param symbol the method name.
|
||||
* @return
|
||||
*/
|
||||
public static ExpressionDependency forMethod(String symbol) {
|
||||
return new ExpressionDependency(DependencyType.METHOD, symbol, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpressionDependency} for a property or field.
|
||||
*
|
||||
* @param symbol the property/field name.
|
||||
* @return
|
||||
*/
|
||||
public static ExpressionDependency forPropertyOrField(String symbol) {
|
||||
return new ExpressionDependency(DependencyType.PROPERTY, symbol, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a nesting {@code level} with the {@link ExpressionDependency}. Returns
|
||||
*
|
||||
* @param level
|
||||
* @return
|
||||
*/
|
||||
public ExpressionDependency nest(int level) {
|
||||
return nestLevel == level ? this : new ExpressionDependency(type, symbol, level);
|
||||
}
|
||||
|
||||
public boolean isNested() {
|
||||
return !isTopLevel();
|
||||
}
|
||||
|
||||
public boolean isTopLevel() {
|
||||
return this.nestLevel == 0;
|
||||
}
|
||||
|
||||
public boolean isMethod() {
|
||||
return this.type == DependencyType.METHOD;
|
||||
}
|
||||
|
||||
public boolean isPropertyOrField() {
|
||||
return this.type == DependencyType.PROPERTY;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ExpressionDependency)) {
|
||||
return false;
|
||||
}
|
||||
ExpressionDependency that = (ExpressionDependency) o;
|
||||
if (nestLevel != that.nestLevel) {
|
||||
return false;
|
||||
}
|
||||
if (type != that.type) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(symbol, that.symbol);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(type);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(symbol);
|
||||
result = 31 * result + nestLevel;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpressionDependency{" + "type=" + type + ", symbol='" + symbol + '\'' + ", nestLevel=" + nestLevel + '}';
|
||||
}
|
||||
|
||||
enum DependencyType {
|
||||
PROPERTY, METHOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.spel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -22,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -32,6 +34,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.spel.EvaluationContextExtensionInformation.ExtensionTypeInformation;
|
||||
import org.springframework.data.spel.EvaluationContextExtensionInformation.RootObjectInformation;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.spel.spi.ExtensionIdAware;
|
||||
import org.springframework.data.spel.spi.Function;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.Optionals;
|
||||
@@ -62,7 +65,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
|
||||
private final Map<Class<?>, EvaluationContextExtensionInformation> extensionInformationCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final Lazy<? extends Collection<? extends EvaluationContextExtension>> extensions;
|
||||
private final Lazy<? extends Collection<? extends ExtensionIdAware>> extensions;
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
ExtensionAwareEvaluationContextProvider() {
|
||||
@@ -87,28 +90,40 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwareEvaluationContextProvider(Collection<? extends EvaluationContextExtension> extensions) {
|
||||
public ExtensionAwareEvaluationContextProvider(Collection<? extends ExtensionIdAware> extensions) {
|
||||
this(Lazy.of(extensions));
|
||||
}
|
||||
|
||||
public ExtensionAwareEvaluationContextProvider(
|
||||
Lazy<? extends Collection<? extends EvaluationContextExtension>> extensions) {
|
||||
Lazy<? extends Collection<? extends ExtensionIdAware>> extensions) {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.support.EvaluationContextProvider#getEvaluationContext()
|
||||
* @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(Object)
|
||||
*/
|
||||
@Override
|
||||
public StandardEvaluationContext getEvaluationContext(Object rootObject) {
|
||||
return doGetEvaluationContext(rootObject, getExtensions(it -> true));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(Object, Collection, ExpressionDependencies)
|
||||
*/
|
||||
@Override
|
||||
public StandardEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
return doGetEvaluationContext(rootObject, getExtensions(it -> dependencies.stream().anyMatch(it::provides)));
|
||||
}
|
||||
|
||||
StandardEvaluationContext doGetEvaluationContext(Object rootObject,
|
||||
Collection<? extends EvaluationContextExtension> extensions) {
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
|
||||
if (beanFactory != null) {
|
||||
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
}
|
||||
|
||||
ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(extensions.get());
|
||||
ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(extensions);
|
||||
|
||||
context.addPropertyAccessor(accessor);
|
||||
context.addPropertyAccessor(new ReflectivePropertyAccessor());
|
||||
@@ -121,14 +136,37 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
return context;
|
||||
}
|
||||
|
||||
Collection<? extends ExtensionIdAware> getExtensions() {
|
||||
return this.extensions.get();
|
||||
}
|
||||
|
||||
Collection<? extends EvaluationContextExtension> getExtensions(
|
||||
Predicate<EvaluationContextExtensionInformation> extensionFilter) {
|
||||
|
||||
Collection<EvaluationContextExtension> extensionsToUse = new ArrayList<>();
|
||||
|
||||
for (ExtensionIdAware candidate : this.extensions.get()) {
|
||||
|
||||
if (candidate instanceof EvaluationContextExtension) {
|
||||
|
||||
EvaluationContextExtension extension = (EvaluationContextExtension) candidate;
|
||||
if (extensionFilter.test(getOrCreateInformation(extension))) {
|
||||
extensionsToUse.add(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensionsToUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all {@link EvaluationContextExtension} instances from the given {@link ListableBeanFactory}.
|
||||
* Looks up all {@link ExtensionIdAware} instances from the given {@link ListableBeanFactory}.
|
||||
*
|
||||
* @param beanFactory must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Collection<? extends EvaluationContextExtension> getExtensionsFrom(ListableBeanFactory beanFactory) {
|
||||
return beanFactory.getBeansOfType(EvaluationContextExtension.class, true, false).values();
|
||||
private static Collection<? extends ExtensionIdAware> getExtensionsFrom(ListableBeanFactory beanFactory) {
|
||||
return beanFactory.getBeansOfType(ExtensionIdAware.class, true, false).values();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,18 +176,27 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
* @param extension must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private EvaluationContextExtensionInformation getOrCreateInformation(EvaluationContextExtension extension) {
|
||||
EvaluationContextExtensionInformation getOrCreateInformation(EvaluationContextExtension extension) {
|
||||
return getOrCreateInformation(extension.getClass());
|
||||
}
|
||||
|
||||
Class<? extends EvaluationContextExtension> extensionType = extension.getClass();
|
||||
|
||||
return extensionInformationCache.computeIfAbsent(extensionType,
|
||||
type -> new EvaluationContextExtensionInformation(extensionType));
|
||||
/**
|
||||
* Looks up the {@link EvaluationContextExtensionInformation} for the given {@link EvaluationContextExtension} from
|
||||
* the cache or creates a new one and caches that for later lookup.
|
||||
*
|
||||
* @param extension must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
EvaluationContextExtensionInformation getOrCreateInformation(Class<? extends EvaluationContextExtension> extension) {
|
||||
return extensionInformationCache.computeIfAbsent(extension,
|
||||
type -> new EvaluationContextExtensionInformation(extension));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link EvaluationContextExtensionAdapter}s for the given {@link EvaluationContextExtension}s.
|
||||
*
|
||||
* @param extensions
|
||||
* @param filter to remove unwanted extensions.
|
||||
* @return
|
||||
*/
|
||||
private List<EvaluationContextExtensionAdapter> toAdapters(
|
||||
@@ -157,7 +204,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
|
||||
return extensions.stream()//
|
||||
.sorted(AnnotationAwareOrderComparator.INSTANCE)//
|
||||
.map(it -> new EvaluationContextExtensionAdapter(it, getOrCreateInformation(it)))//
|
||||
.map(it -> new EvaluationContextExtensionAdapter(it, getOrCreateInformation(it))) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -166,7 +213,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
* @author Oliver Gierke
|
||||
* @see 1.9
|
||||
*/
|
||||
private class ExtensionAwarePropertyAccessor implements PropertyAccessor, MethodResolver {
|
||||
class ExtensionAwarePropertyAccessor implements PropertyAccessor, MethodResolver {
|
||||
|
||||
private final List<EvaluationContextExtensionAdapter> adapters;
|
||||
private final Map<String, EvaluationContextExtensionAdapter> adapterMap;
|
||||
@@ -412,5 +459,10 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
public Map<String, Object> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("EvaluationContextExtensionAdapter for '%s'", getExtensionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.spel;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
|
||||
/**
|
||||
* Provides a way to access a centrally defined potentially shared {@link EvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveEvaluationContextProvider {
|
||||
|
||||
/**
|
||||
* Returns an {@link EvaluationContext} built using the given parameter values.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @return mono emitting the {@link EvaluationContext}.
|
||||
*/
|
||||
Mono<? extends EvaluationContext> getEvaluationContext(Object rootObject);
|
||||
|
||||
/**
|
||||
* Returns a tailored {@link EvaluationContext} built using the given parameter values and considering
|
||||
* {@link ExpressionDependencies.ExpressionDependency expression dependencies}. The returned {@link EvaluationContext}
|
||||
* may contain a reduced visibility of methods and properties/fields according to the required
|
||||
* {@link ExpressionDependencies.ExpressionDependency expression dependencies}.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @param dependencies the requested expression dependencies to be available.
|
||||
* @return mono emitting the {@link EvaluationContext}.
|
||||
* @since 2.4
|
||||
*/
|
||||
default Mono<? extends EvaluationContext> getEvaluationContext(Object rootObject,
|
||||
ExpressionDependencies dependencies) {
|
||||
return getEvaluationContext(rootObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.spel;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.spel.spi.ExtensionIdAware;
|
||||
import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* A reactive {@link EvaluationContextProvider} that assembles an {@link EvaluationContext} from a list of
|
||||
* {@link ReactiveEvaluationContextExtension} and {@link EvaluationContextExtension} instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ReactiveExtensionAwareEvaluationContextProvider implements ReactiveEvaluationContextProvider {
|
||||
|
||||
private static final ResolvableType GENERIC_EXTENSION_TYPE = ResolvableType
|
||||
.forClass(EvaluationContextExtension.class);
|
||||
|
||||
private final ExtensionAwareEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public ReactiveExtensionAwareEvaluationContextProvider() {
|
||||
evaluationContextProvider = new ExtensionAwareEvaluationContextProvider();
|
||||
}
|
||||
|
||||
public ReactiveExtensionAwareEvaluationContextProvider(ListableBeanFactory beanFactory) {
|
||||
evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(beanFactory);
|
||||
}
|
||||
|
||||
public ReactiveExtensionAwareEvaluationContextProvider(Collection<? extends ExtensionIdAware> extensions) {
|
||||
evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(extensions);
|
||||
}
|
||||
|
||||
public ReactiveExtensionAwareEvaluationContextProvider(
|
||||
Lazy<? extends Collection<? extends ExtensionIdAware>> extensions) {
|
||||
evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<StandardEvaluationContext> getEvaluationContext(Object rootObject) {
|
||||
return getExtensions(it -> true) //
|
||||
.map(it -> evaluationContextProvider.doGetEvaluationContext(rootObject, it));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<StandardEvaluationContext> getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
|
||||
return getExtensions(it -> dependencies.stream().anyMatch(it::provides)) //
|
||||
.map(it -> evaluationContextProvider.doGetEvaluationContext(rootObject, it));
|
||||
}
|
||||
|
||||
private Mono<List<EvaluationContextExtension>> getExtensions(
|
||||
Predicate<EvaluationContextExtensionInformation> extensionFilter) {
|
||||
|
||||
Collection<? extends ExtensionIdAware> extensions = evaluationContextProvider.getExtensions();
|
||||
|
||||
return Flux.fromIterable(extensions).concatMap(it -> {
|
||||
|
||||
if (it instanceof EvaluationContextExtension) {
|
||||
EvaluationContextExtension extension = (EvaluationContextExtension) it;
|
||||
EvaluationContextExtensionInformation information = evaluationContextProvider.getOrCreateInformation(extension);
|
||||
|
||||
if (extensionFilter.test(information)) {
|
||||
return Mono.just(extension);
|
||||
}
|
||||
}
|
||||
|
||||
if (it instanceof ReactiveEvaluationContextExtension) {
|
||||
|
||||
ReactiveEvaluationContextExtension extension = (ReactiveEvaluationContextExtension) it;
|
||||
ResolvableType actualType = getExtensionType(it);
|
||||
|
||||
if (actualType.equals(ResolvableType.NONE) || actualType.isAssignableFrom(GENERIC_EXTENSION_TYPE)) {
|
||||
return extension.getExtension();
|
||||
}
|
||||
|
||||
EvaluationContextExtensionInformation information = evaluationContextProvider
|
||||
.getOrCreateInformation((Class) actualType.getRawClass());
|
||||
|
||||
if (extensionFilter.test(information)) {
|
||||
return extension.getExtension();
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return Mono.error(new IllegalStateException("Unsupported extension type: " + it));
|
||||
}).collectList();
|
||||
}
|
||||
|
||||
private ResolvableType getExtensionType(ExtensionIdAware extensionCandidate) {
|
||||
|
||||
ResolvableType extensionType = ResolvableType
|
||||
.forMethodReturnType(ReflectionUtils.findRequiredMethod(extensionCandidate.getClass(), "getExtension"))
|
||||
.getGeneric(0);
|
||||
|
||||
return extensionType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,20 +25,16 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* SPI to allow adding a set of properties and function definitions accessible via the root of an
|
||||
* {@link EvaluationContext} provided by a {@link ExtensionAwareQueryMethodEvaluationContextProvider}.
|
||||
* <p>
|
||||
* Extensions can be ordered by following Spring's {@link org.springframework.core.Ordered} conventions.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.Order
|
||||
*/
|
||||
public interface EvaluationContextExtension {
|
||||
|
||||
/**
|
||||
* Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and
|
||||
* thus overcome ambiguities in case multiple extensions expose properties with the same name.
|
||||
*
|
||||
* @return the extension id, must not be {@literal null}.
|
||||
*/
|
||||
String getExtensionId();
|
||||
public interface EvaluationContextExtension extends ExtensionIdAware {
|
||||
|
||||
/**
|
||||
* Returns the properties exposed by the extension.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.spel.spi;
|
||||
|
||||
/**
|
||||
* Marker interface for Spring Data {@link org.springframework.expression.EvaluationContext} extensions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
* @see EvaluationContextExtension
|
||||
* @see ReactiveEvaluationContextExtension
|
||||
*/
|
||||
public interface ExtensionIdAware {
|
||||
|
||||
/**
|
||||
* Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and
|
||||
* thus overcome ambiguities in case multiple extensions expose properties with the same name.
|
||||
*
|
||||
* @return the extension id, must not be {@literal null}.
|
||||
*/
|
||||
String getExtensionId();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.spel.spi;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
|
||||
/**
|
||||
* SPI to resolve a {@link EvaluationContextExtension} to make it accessible via the root of an
|
||||
* {@link EvaluationContext} provided by a {@link ExtensionAwareQueryMethodEvaluationContextProvider}.
|
||||
* <p>
|
||||
* Extensions can be ordered by following Spring's {@link org.springframework.core.Ordered} conventions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
* @see EvaluationContextExtension
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.Order
|
||||
*/
|
||||
public interface ReactiveEvaluationContextExtension extends ExtensionIdAware {
|
||||
|
||||
/**
|
||||
* Returns the {@link EvaluationContextExtension} to be consumed during the actual execution. It's strongly
|
||||
* recommended to declare the most concrete type possible as return type of the implementation method. This will allow
|
||||
* us to obtain the necessary metadata once and not for every evaluation.
|
||||
*
|
||||
* @return the resolved {@link EvaluationContextExtension}. Publishers emitting no value will be skipped.
|
||||
*/
|
||||
Mono<? extends EvaluationContextExtension> getExtension();
|
||||
}
|
||||
Reference in New Issue
Block a user