DATACMNS-533 - Implemented handling of root objects.
Refactored the implementation of the dynamic lookup of properties and functions on EvaluationContextExtensions. All the reflective lookup logic has moved into an EvaluationContextExtensionAdapter. We now also distinguish between the meta-information that can be looked up once and cached (so that we avoid reflection overhead for subsequent context creations). Related pull request: spring-projects/spring-data-jpa#101 Related ticket: DATAJPA-564
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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.repository.query;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.ExtensionTypeInformation.PublicMethodAndFieldFilter;
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.repository.query.spi.Function;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.FieldCallback;
|
||||
import org.springframework.util.ReflectionUtils.FieldFilter;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
/**
|
||||
* Inspects the configured {@link EvaluationContextExtension} type for static methods and fields to avoid repeated
|
||||
* reflection lookups. Also inspects the return type of the {@link EvaluationContextExtension#getRootObject()} method
|
||||
* and captures the methods declared on it as {@link Function}s.
|
||||
* <p>
|
||||
* The type basically allows us to cache the type based information within
|
||||
* {@link ExtensionAwareEvaluationContextProvider} to avoid repeated reflection lookups for every creation of an
|
||||
* {@link EvaluationContext}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
*/
|
||||
class EvaluationContextExtensionInformation {
|
||||
|
||||
private final ExtensionTypeInformation extensionTypeInformation;
|
||||
private final RootObjectInformation rootObjectInformation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EvaluationContextExtension} for the given extension type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public EvaluationContextExtensionInformation(Class<? extends EvaluationContextExtension> type) {
|
||||
|
||||
Assert.notNull(type, "Extension type must not be null!");
|
||||
Class<?> rootObjectType = getRootObjectMethod(type).getReturnType();
|
||||
|
||||
this.rootObjectInformation = Object.class.equals(rootObjectType) ? null : new RootObjectInformation(rootObjectType);
|
||||
this.extensionTypeInformation = new ExtensionTypeInformation(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ExtensionTypeInformation} for the extension.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ExtensionTypeInformation getExtensionTypeInformation() {
|
||||
return this.extensionTypeInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RootObjectInformation} for the given target object. If the information has been pre-computed
|
||||
* earlier, the existing one will be used.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public RootObjectInformation getRootObjectInformation(Object target) {
|
||||
return target == null ? RootObjectInformation.NONE : rootObjectInformation == null ? new RootObjectInformation(
|
||||
target.getClass()) : rootObjectInformation;
|
||||
}
|
||||
|
||||
private static Method getRootObjectMethod(Class<?> type) {
|
||||
|
||||
try {
|
||||
return type.getMethod("getRootObject");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class ExtensionTypeInformation {
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
private final Map<String, Function> functions;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionTypeInformation} fir the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionTypeInformation(Class<? extends EvaluationContextExtension> type) {
|
||||
|
||||
Assert.notNull(type, "Extension type must not be null!");
|
||||
|
||||
this.functions = discoverDeclaredFunctions(type);
|
||||
this.properties = discoverDeclaredProperties(type, PublicMethodAndFieldFilter.STATIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the statically defined properties of the extension type.
|
||||
*
|
||||
* @return the properties will never be {@literal null}.
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the statically exposed functions of the extension type.
|
||||
*
|
||||
* @return the functions will never be {@literal null}.
|
||||
*/
|
||||
public Map<String, Function> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
private static Map<String, Function> discoverDeclaredFunctions(Class<?> type) {
|
||||
|
||||
final Map<String, Function> map = new HashMap<String, Function>();
|
||||
|
||||
ReflectionUtils.doWithMethods(type, new MethodCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
|
||||
map.put(method.getName(), new Function(method, null));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map.isEmpty() ? Collections.<String, Function> emptyMap() : Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
static class PublicMethodAndFieldFilter implements MethodFilter, FieldFilter {
|
||||
|
||||
public static PublicMethodAndFieldFilter STATIC = new PublicMethodAndFieldFilter(true);
|
||||
public static PublicMethodAndFieldFilter NON_STATIC = new PublicMethodAndFieldFilter(false);
|
||||
|
||||
private final boolean staticOnly;
|
||||
|
||||
/**
|
||||
* @param staticOnly
|
||||
*/
|
||||
public PublicMethodAndFieldFilter(boolean forStatic) {
|
||||
this.staticOnly = forStatic;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.ReflectionUtils.MethodFilter#matches(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
|
||||
if (ReflectionUtils.isObjectMethod(method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean methodStatic = Modifier.isStatic(method.getModifiers());
|
||||
boolean staticMatch = staticOnly ? methodStatic : !methodStatic;
|
||||
|
||||
return Modifier.isPublic(method.getModifiers()) && staticMatch;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.ReflectionUtils.FieldFilter#matches(java.lang.reflect.Field)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(Field field) {
|
||||
|
||||
if (field.getDeclaringClass().equals(Object.class)) {
|
||||
|
||||
}
|
||||
|
||||
boolean fieldStatic = Modifier.isStatic(field.getModifiers());
|
||||
boolean staticMatch = staticOnly ? fieldStatic : !fieldStatic;
|
||||
|
||||
return Modifier.isPublic(field.getModifiers()) && staticMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the root object of an extension.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class RootObjectInformation {
|
||||
|
||||
static RootObjectInformation NONE = new RootObjectInformation(Object.class);
|
||||
|
||||
private final Map<String, Method> accessors;
|
||||
private final Collection<Method> methods;
|
||||
private final Collection<Field> fields;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RootObjectInformation} for the given type. Inspects public methods and fields to register
|
||||
* them as {@link Function}s and properties.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public RootObjectInformation(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
this.accessors = new HashMap<String, Method>();
|
||||
this.methods = new HashSet<Method>();
|
||||
this.fields = new ArrayList<Field>();
|
||||
|
||||
if (Object.class.equals(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(type);
|
||||
|
||||
ReflectionUtils.doWithMethods(type, new MethodCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
RootObjectInformation.this.methods.add(method);
|
||||
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
if (method.equals(descriptor.getReadMethod())) {
|
||||
RootObjectInformation.this.accessors.put(descriptor.getName(), method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, PublicMethodAndFieldFilter.NON_STATIC);
|
||||
|
||||
ReflectionUtils.doWithFields(type, new FieldCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
RootObjectInformation.this.fields.add(field);
|
||||
}
|
||||
}, PublicMethodAndFieldFilter.NON_STATIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Function} instances that wrap method invocations on the given target object.
|
||||
*
|
||||
* @param target can be {@literal null}.
|
||||
* @return the methods
|
||||
*/
|
||||
public Map<String, Function> getFunctions(Object target) {
|
||||
|
||||
if (target == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, Function> functions = new HashMap<String, Function>(methods.size());
|
||||
|
||||
for (Method method : methods) {
|
||||
functions.put(method.getName(), new Function(method, target));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(functions);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties of the target object. This will also include {@link Function} instances for all properties
|
||||
* with accessor methods that need to be resolved downstream.
|
||||
*
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, Object> getProperties(Object target) {
|
||||
|
||||
if (target == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
|
||||
for (Entry<String, Method> method : accessors.entrySet()) {
|
||||
properties.put(method.getKey(), new Function(method.getValue(), target));
|
||||
}
|
||||
|
||||
for (Field field : fields) {
|
||||
properties.put(field.getName(), ReflectionUtils.getField(field, target));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> discoverDeclaredProperties(Class<?> type, FieldFilter filter) {
|
||||
|
||||
final Map<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
ReflectionUtils.doWithFields(type, new FieldCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
map.put(field.getName(), field.get(null));
|
||||
}
|
||||
}, filter);
|
||||
|
||||
return map.isEmpty() ? Collections.<String, Object> emptyMap() : Collections.unmodifiableMap(map);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -30,7 +30,10 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.ExtensionTypeInformation;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.RootObjectInformation;
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.repository.query.spi.Function;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
@@ -43,7 +46,6 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.TypeUtils;
|
||||
|
||||
/**
|
||||
* An {@link EvaluationContextProvider} that assembles an {@link EvaluationContext} from a list of
|
||||
@@ -55,28 +57,28 @@ import org.springframework.util.TypeUtils;
|
||||
*/
|
||||
public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider, ApplicationContextAware {
|
||||
|
||||
private List<EvaluationContextExtension> extensions;
|
||||
private final Map<Class<?>, EvaluationContextExtensionInformation> extensionInformationCache = new HashMap<Class<?>, EvaluationContextExtensionInformation>();
|
||||
|
||||
private List<? extends EvaluationContextExtension> extensions;
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwareEvaluationContextProvider}. Extensions are being looked up lazily from the
|
||||
* {@link BeanFactory} configured.
|
||||
*/
|
||||
public ExtensionAwareEvaluationContextProvider() {}
|
||||
public ExtensionAwareEvaluationContextProvider() {
|
||||
this.extensions = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwareEvaluationContextProvider} for the given {@link EvaluationContextExtension}s.
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
* @param adapters must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwareEvaluationContextProvider(List<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
Assert.notNull(extensions, "List of EvaluationContextExtensions must not be null!");
|
||||
|
||||
List<EvaluationContextExtension> extensionsToSet = new ArrayList<EvaluationContextExtension>(extensions);
|
||||
Collections.sort(extensionsToSet, AnnotationAwareOrderComparator.INSTANCE);
|
||||
|
||||
this.extensions = Collections.unmodifiableList(extensionsToSet);
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -152,7 +154,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private List<EvaluationContextExtension> getExtensions() {
|
||||
private List<? extends EvaluationContextExtension> getExtensions() {
|
||||
|
||||
if (this.extensions != null) {
|
||||
return this.extensions;
|
||||
@@ -163,57 +165,81 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
return this.extensions;
|
||||
}
|
||||
|
||||
List<EvaluationContextExtension> extensions = new ArrayList<EvaluationContextExtension>(beanFactory.getBeansOfType(
|
||||
this.extensions = new ArrayList<EvaluationContextExtension>(beanFactory.getBeansOfType(
|
||||
EvaluationContextExtension.class, true, false).values());
|
||||
Collections.sort(extensions, AnnotationAwareOrderComparator.INSTANCE);
|
||||
this.extensions = extensions;
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private EvaluationContextExtensionInformation getOrCreateInformation(EvaluationContextExtension extension) {
|
||||
|
||||
Class<? extends EvaluationContextExtension> extensionType = extension.getClass();
|
||||
EvaluationContextExtensionInformation information = extensionInformationCache.get(extensionType);
|
||||
|
||||
if (information != null) {
|
||||
return information;
|
||||
}
|
||||
|
||||
information = new EvaluationContextExtensionInformation(extensionType);
|
||||
extensionInformationCache.put(extensionType, information);
|
||||
return information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link EvaluationContextExtensionAdapter}s for the given {@link EvaluationContextExtension}s.
|
||||
*
|
||||
* @param extensions
|
||||
* @return
|
||||
*/
|
||||
private List<EvaluationContextExtensionAdapter> toAdapters(Collection<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
List<EvaluationContextExtension> extensionsToSet = new ArrayList<EvaluationContextExtension>(extensions);
|
||||
Collections.sort(extensionsToSet, AnnotationAwareOrderComparator.INSTANCE);
|
||||
|
||||
List<EvaluationContextExtensionAdapter> adapters = new ArrayList<EvaluationContextExtensionAdapter>(
|
||||
extensions.size());
|
||||
|
||||
for (EvaluationContextExtension extension : extensionsToSet) {
|
||||
adapters.add(new EvaluationContextExtensionAdapter(extension, getOrCreateInformation(extension)));
|
||||
}
|
||||
|
||||
return adapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @see 1.9
|
||||
*/
|
||||
private static class ExtensionAwarePropertyAccessor implements PropertyAccessor, MethodResolver {
|
||||
private class ExtensionAwarePropertyAccessor implements PropertyAccessor, MethodResolver {
|
||||
|
||||
private final Map<String, EvaluationContextExtension> extensionMap;
|
||||
private final List<EvaluationContextExtension> extensions;
|
||||
private final Map<String, Object> functions;
|
||||
private final List<EvaluationContextExtensionAdapter> adapters;
|
||||
private final Map<String, EvaluationContextExtensionAdapter> adapterMap;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwarePropertyAccessor} for the given {@link EvaluationContextExtension}s.
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
* @param adapters must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwarePropertyAccessor(List<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
Assert.notNull(extensions, "Extensions must not be null!");
|
||||
|
||||
Map<String, Object> functions = new HashMap<String, Object>();
|
||||
this.adapters = toAdapters(extensions);
|
||||
this.adapterMap = new HashMap<String, EvaluationContextExtensionAdapter>(extensions.size());
|
||||
|
||||
for (EvaluationContextExtension ext : extensions) {
|
||||
|
||||
Map<String, Method> extFunctions = ext.getFunctions();
|
||||
|
||||
if (ext.getExtensionId() != null) {
|
||||
functions.put(ext.getExtensionId(), extFunctions);
|
||||
}
|
||||
|
||||
functions.putAll(extFunctions);
|
||||
for (EvaluationContextExtensionAdapter adapter : adapters) {
|
||||
this.adapterMap.put(adapter.getExtensionId(), adapter);
|
||||
}
|
||||
|
||||
this.functions = functions;
|
||||
|
||||
this.extensions = new ArrayList<EvaluationContextExtension>(extensions);
|
||||
Collections.reverse(this.extensions);
|
||||
|
||||
this.extensionMap = new HashMap<String, EvaluationContextExtension>(extensions.size());
|
||||
|
||||
for (EvaluationContextExtension extension : extensions) {
|
||||
this.extensionMap.put(extension.getExtensionId(), extension);
|
||||
}
|
||||
Collections.reverse(this.adapters);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -227,11 +253,11 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
return true;
|
||||
}
|
||||
|
||||
if (extensionMap.containsKey(name)) {
|
||||
if (adapterMap.containsKey(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (EvaluationContextExtension extension : extensions) {
|
||||
for (EvaluationContextExtensionAdapter extension : adapters) {
|
||||
if (extension.getProperties().containsKey(name)) {
|
||||
return true;
|
||||
}
|
||||
@@ -247,20 +273,20 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
|
||||
if (target instanceof EvaluationContextExtension) {
|
||||
return new TypedValue(((EvaluationContextExtension) target).getProperties().get(name));
|
||||
if (target instanceof EvaluationContextExtensionAdapter) {
|
||||
return lookupPropertyFrom(((EvaluationContextExtensionAdapter) target), name);
|
||||
}
|
||||
|
||||
if (extensionMap.containsKey(name)) {
|
||||
return new TypedValue(extensionMap.get(name));
|
||||
if (adapterMap.containsKey(name)) {
|
||||
return new TypedValue(adapterMap.get(name));
|
||||
}
|
||||
|
||||
for (EvaluationContextExtension extension : extensions) {
|
||||
for (EvaluationContextExtensionAdapter extension : adapters) {
|
||||
|
||||
Map<String, Object> properties = extension.getProperties();
|
||||
|
||||
if (properties.containsKey(name)) {
|
||||
return new TypedValue(properties.get(name));
|
||||
return lookupPropertyFrom(extension, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,44 +298,23 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
* @see org.springframework.expression.MethodResolver#resolve(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
|
||||
public MethodExecutor resolve(EvaluationContext context, Object target, final String name,
|
||||
List<TypeDescriptor> argumentTypes) throws AccessException {
|
||||
|
||||
final Method function = targetObject instanceof Map && ((Map<?, ?>) targetObject).containsKey(name) ? (Method) ((Map<?, ?>) targetObject)
|
||||
.get(name) : (Method) functions.get(name);
|
||||
|
||||
if (function == null) {
|
||||
return null;
|
||||
if (target instanceof EvaluationContextExtensionAdapter) {
|
||||
return getMethodExecutor((EvaluationContextExtensionAdapter) target, name, argumentTypes);
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = function.getParameterTypes();
|
||||
if (parameterTypes.length != argumentTypes.size()) {
|
||||
return null;
|
||||
}
|
||||
for (EvaluationContextExtensionAdapter adapter : adapters) {
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
if (!TypeUtils.isAssignable(parameterTypes[i], argumentTypes.get(i).getType())) {
|
||||
return null;
|
||||
MethodExecutor executor = getMethodExecutor(adapter, name, argumentTypes);
|
||||
|
||||
if (executor != null) {
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
||||
return new MethodExecutor() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.expression.MethodExecutor#execute(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
|
||||
|
||||
try {
|
||||
return new TypedValue(function.invoke(null, arguments));
|
||||
} catch (Exception e) {
|
||||
throw new SpelEvaluationException(e, SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, function.getName(),
|
||||
function.getDeclaringClass());
|
||||
}
|
||||
}
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -338,5 +343,165 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MethodExecutor}
|
||||
*
|
||||
* @param adapter
|
||||
* @param name
|
||||
* @param argumentTypes
|
||||
* @return
|
||||
*/
|
||||
private MethodExecutor getMethodExecutor(EvaluationContextExtensionAdapter adapter, String name,
|
||||
List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
Map<String, Function> functions = adapter.getFunctions();
|
||||
|
||||
if (!functions.containsKey(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Function function = functions.get(name);
|
||||
|
||||
if (!function.supports(argumentTypes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FunctionMethodExecutor(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the property value for the property of the given name from the given extension. Takes care of resolving
|
||||
* {@link Function} values transitively.
|
||||
*
|
||||
* @param extension must not be {@literal null}.
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private TypedValue lookupPropertyFrom(EvaluationContextExtensionAdapter extension, String name) {
|
||||
|
||||
Object value = extension.getProperties().get(name);
|
||||
|
||||
if (!(value instanceof Function)) {
|
||||
return new TypedValue(value);
|
||||
}
|
||||
|
||||
Function function = (Function) value;
|
||||
|
||||
try {
|
||||
return new TypedValue(function.invoke(new Object[0]));
|
||||
} catch (Exception e) {
|
||||
throw new SpelEvaluationException(e, SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, name,
|
||||
function.getDeclaringClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MethodExecutor} to invoke {@link Function} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
*/
|
||||
private static class FunctionMethodExecutor implements MethodExecutor {
|
||||
|
||||
private final Function function;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FunctionMethodExecutor} for the given {@link Function}.
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
*/
|
||||
public FunctionMethodExecutor(Function function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.expression.MethodExecutor#execute(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
|
||||
|
||||
try {
|
||||
return new TypedValue(function.invoke(arguments));
|
||||
} catch (Exception e) {
|
||||
throw new SpelEvaluationException(e, SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, function.getName(),
|
||||
function.getDeclaringClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to expose a unified view on {@link EvaluationContextExtension} based on some reflective inspection of the
|
||||
* extension (see {@link EvaluationContextExtensionInformation}) as well as the values exposed by the extension
|
||||
* itself.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
*/
|
||||
private static class EvaluationContextExtensionAdapter {
|
||||
|
||||
private final EvaluationContextExtension extension;
|
||||
|
||||
private final Map<String, Function> functions;
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EvaluationContextExtensionAdapter} for the given {@link EvaluationContextExtension} and
|
||||
* {@link EvaluationContextExtensionInformation}.
|
||||
*
|
||||
* @param extension must not be {@literal null}.
|
||||
* @param information must not be {@literal null}.
|
||||
*/
|
||||
public EvaluationContextExtensionAdapter(EvaluationContextExtension extension,
|
||||
EvaluationContextExtensionInformation information) {
|
||||
|
||||
Assert.notNull(extension, "Extenstion must not be null!");
|
||||
Assert.notNull(information, "Extension information must not be null!");
|
||||
|
||||
Object target = extension.getRootObject();
|
||||
ExtensionTypeInformation extensionTypeInformation = information.getExtensionTypeInformation();
|
||||
RootObjectInformation rootObjectInformation = information.getRootObjectInformation(target);
|
||||
|
||||
this.functions = new HashMap<String, Function>();
|
||||
this.functions.putAll(extensionTypeInformation.getFunctions());
|
||||
this.functions.putAll(rootObjectInformation.getFunctions(target));
|
||||
this.functions.putAll(extension.getFunctions());
|
||||
|
||||
this.properties = new HashMap<String, Object>();
|
||||
this.properties.putAll(extensionTypeInformation.getProperties());
|
||||
this.properties.putAll(rootObjectInformation.getProperties(target));
|
||||
this.properties.putAll(extension.getProperties());
|
||||
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension identifier.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getExtensionId() {
|
||||
return extension.getExtensionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all functions exposed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Function> getFunctions() {
|
||||
return this.functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all properties exposed. Note, the value of a property can be a {@link Function} in turn
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query.spi;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.FieldCallback;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* A base class for {@link EvaluationContextExtension}s.
|
||||
*
|
||||
@@ -35,61 +27,13 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
*/
|
||||
public abstract class EvaluationContextExtensionSupport implements EvaluationContextExtension {
|
||||
|
||||
private final Map<String, Object> declaredProperties;
|
||||
private final Map<String, Method> declaredFunctions;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EvaluationContextExtensionSupport}.
|
||||
*/
|
||||
public EvaluationContextExtensionSupport() {
|
||||
|
||||
this.declaredProperties = discoverDeclaredProperties();
|
||||
this.declaredFunctions = discoverDeclaredFunctions();
|
||||
}
|
||||
|
||||
private Map<String, Object> discoverDeclaredProperties() {
|
||||
|
||||
final Map<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
ReflectionUtils.doWithFields(getClass(), new FieldCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) {
|
||||
map.put(field.getName(), field.get(null));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map.isEmpty() ? Collections.<String, Object> emptyMap() : Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private Map<String, Method> discoverDeclaredFunctions() {
|
||||
|
||||
final Map<String, Method> map = new HashMap<String, Method>();
|
||||
|
||||
ReflectionUtils.doWithMethods(getClass(), new MethodCallback() {
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
|
||||
map.put(method.getName(), method);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map.isEmpty() ? Collections.<String, Method> emptyMap() : Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.EvaluationContextExtension#getProperties()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return this.declaredProperties;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -97,7 +41,16 @@ public abstract class EvaluationContextExtensionSupport implements EvaluationCon
|
||||
* @see org.springframework.data.repository.query.EvaluationContextExtension#getFunctions()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Method> getFunctions() {
|
||||
return this.declaredFunctions;
|
||||
public Map<String, Function> getFunctions() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.spi.EvaluationContextExtension#getRootObject()
|
||||
*/
|
||||
@Override
|
||||
public Object getRootObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user