DATACMNS-630 - Integrate projection infrastructure from Spring Data REST.

Ported the projection infrastructure previously residing in Spring Data REST and extended it by defaulting to a Map-backed source to store and retrieve data.

Separated out the SpEL based functionality mostly for SOC-reasons and easier testability.

Related tickets: DATAREST-437, DATACMNS-618, DATACMNS-89.
This commit is contained in:
Oliver Gierke
2015-01-10 17:14:57 +01:00
parent 758d4b98a1
commit a98d5c7ec6
13 changed files with 1693 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2015 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.projection;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link MethodInterceptor} to support accessor methods to store and retrieve values from a {@link Map}.
*
* @author Oliver Gierke
* @since 1.10
*/
class MapAccessingMethodInterceptor implements MethodInterceptor {
private final Map<String, Object> map;
/**
* Creates a new {@link MapAccessingMethodInterceptor} for the given {@link Map}.
*
* @param map must not be {@literal null}.
*/
public MapAccessingMethodInterceptor(Map<String, Object> map) {
Assert.notNull(map, "Map must not be null!");
this.map = map;
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (ReflectionUtils.isObjectMethod(method)) {
return invocation.proceed();
}
Accessor accessor = new Accessor(method);
if (accessor.isGetter()) {
return map.get(accessor.getPropertyName());
} else if (accessor.isSetter()) {
map.put(accessor.getPropertyName(), invocation.getArguments()[0]);
return null;
}
throw new IllegalStateException("Should never get here!");
}
/**
* Helper value to abstract an accessor.
*
* @author Oliver Gierke
*/
private static final class Accessor {
private final PropertyDescriptor descriptor;
private final Method method;
/**
* Creates an {@link Accessor} for the given {@link Method}.
*
* @param method must not be {@literal null}.
* @throws IllegalArgumentException in case the given method is not an accessor method.
*/
public Accessor(Method method) {
Assert.notNull(method, "Method must not be null!");
this.descriptor = BeanUtils.findPropertyForMethod(method);
this.method = method;
Assert.notNull(descriptor, String.format("Invoked method %s is no accessor method!", method));
}
/**
* Returns whether the acessor is a getter.
*
* @return
*/
public boolean isGetter() {
return method.equals(descriptor.getReadMethod());
}
/**
* Returns whether the accessor is a setter.
*
* @return
*/
public boolean isSetter() {
return method.equals(descriptor.getWriteMethod());
}
/**
* Returns the name of the property this accessor handles.
*
* @return will never be {@literal null}.
*/
public String getPropertyName() {
return descriptor.getName();
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2014-2015 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.projection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.CollectionFactory;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link MethodInterceptor} to delegate the invocation to a different {@link MethodInterceptor} but creating a
* projecting proxy in case the returned value is not of the return type of the invoked method.
*
* @author Oliver Gierke
* @since 1.10
*/
class ProjectingMethodInterceptor implements MethodInterceptor {
private final ProjectionFactory factory;
private final MethodInterceptor delegate;
/**
* Creates a new {@link ProjectingMethodInterceptor} using the given {@link ProjectionFactory} and delegate
* {@link MethodInterceptor}.
*
* @param factory the {@link ProjectionFactory} to use to create projections if types do not match, must not be
* {@literal null}..
* @param delegate the {@link MethodInterceptor} to trigger to create the source value, must not be {@literal null}..
*/
public ProjectingMethodInterceptor(ProjectionFactory factory, MethodInterceptor delegate) {
Assert.notNull(factory, "ProjectionFactory must not be null!");
Assert.notNull(delegate, "Delegate MethodInterceptor must not be null!");
this.factory = factory;
this.delegate = delegate;
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = delegate.invoke(invocation);
if (result == null) {
return null;
}
TypeInformation<?> type = ClassTypeInformation.fromReturnTypeOf(invocation.getMethod());
if (type.isCollectionLike()) {
return projectCollectionElements(asCollection(result), type);
} else if (type.isMap()) {
return projectMapValues((Map<?, ?>) result, type);
} else {
return getProjection(result, type.getType());
}
}
/**
* Creates projections of the given {@link Collection}'s elements if necessary and returns a new collection containing
* the projection results.
*
* @param sources must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
private Collection<Object> projectCollectionElements(Collection<?> sources, TypeInformation<?> type) {
Collection<Object> result = CollectionFactory.createCollection(type.getType(), sources.size());
for (Object source : sources) {
result.add(getProjection(source, type.getComponentType().getType()));
}
return result;
}
/**
* Creates projections of the given {@link Map}'s values if necessary and returns an new {@link Map} with the handled
* values.
*
* @param sources must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
private Map<Object, Object> projectMapValues(Map<?, ?> sources, TypeInformation<?> type) {
Map<Object, Object> result = CollectionFactory.createMap(type.getType(), sources.size());
for (Entry<?, ?> source : sources.entrySet()) {
result.put(source.getKey(), getProjection(source.getValue(), type.getMapValueType().getType()));
}
return result;
}
private Object getProjection(Object result, Class<?> returnType) {
return ClassUtils.isAssignable(returnType, result.getClass()) ? result : factory.createProjection(returnType,
result);
}
/**
* Turns the given value into a {@link Collection}. Will turn an array into a collection an wrap all other values into
* a single-element collection.
*
* @param source must not be {@literal null}.
* @return
*/
private static Collection<?> asCollection(Object source) {
Assert.notNull(source, "Source object must not be null!");
if (source instanceof Collection) {
return (Collection<?>) source;
} else if (source.getClass().isArray()) {
return Arrays.asList((Object[]) source);
} else {
return Collections.singleton(source);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2015 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.projection;
import java.util.List;
/**
* A factory to create projecting instances for other objects usually used to allow easy creation of representation
* projections to define which properties of a domain objects shall be exported in which way.
*
* @author Oliver Gierke
* @since 1.10
*/
public interface ProjectionFactory {
/**
* Creates a projection of the given type for the given source object. The individual mapping strategy is defined by
* the implementations.
*
* @param projectionType the type to create, must not be {@literal null}.
* @param source the object to create a projection for, can be {@literal null}
* @return
*/
<T> T createProjection(Class<T> projectionType, Object source);
/**
* Creates a pojection instance for the given type.
*
* @param projectionType the type to create, must not be {@literal null}.
* @return
*/
<T> T createProjection(Class<T> projectionType);
/**
* Returns the properties that will be consumed by the given projection type.
*
* @param projectionType must not be {@literal null}.
* @return
*/
List<String> getInputProperties(Class<?> projectionType);
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2014-2015 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.projection;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Method interceptor to forward a delegation to bean property accessor methods to the property of a given target.
*
* @author Oliver Gierke
* @since 1.10
*/
class PropertyAccessingMethodInterceptor implements MethodInterceptor {
private final BeanWrapper target;
/**
* Creates a new {@link PropertyAccessingMethodInterceptor} for the given target object.
*
* @param target must not be {@literal null}.
*/
public PropertyAccessingMethodInterceptor(Object target) {
Assert.notNull(target, "Proxy target must not be null!");
this.target = new DirectFieldAccessFallbackBeanWrapper(target);
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (ReflectionUtils.isObjectMethod(method)) {
return invocation.proceed();
}
PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(method);
if (descriptor == null) {
throw new IllegalStateException("Invoked method is not a property accessor!");
}
return target.getPropertyValue(descriptor.getName());
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2014-2105 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.projection;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* A {@link ProjectionFactory} to create JDK proxies to back interfaces and handle method invocations on them. By
* default accessor methods are supported. In case the delegating lookups result in an object of different type that the
* projection interface method's return type, another projection will be created to transparently mitigate between the
* types.
*
* @author Oliver Gierke
* @see SpelAwareProxyProjectionFactory
* @since 1.10
*/
class ProxyProjectionFactory implements ProjectionFactory {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.projection.ProjectionFactory#createProjection(java.lang.Object, java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T> T createProjection(Class<T> projectionType, Object source) {
Assert.notNull(projectionType, "Projection type must not be null!");
Assert.isTrue(projectionType.isInterface(), "Projection type must be an interface!");
if (source == null) {
return null;
}
ProxyFactory factory = new ProxyFactory();
factory.setTarget(source);
factory.setOpaque(true);
factory.setInterfaces(projectionType, TargetClassAware.class);
factory.addAdvice(new TargetClassAwareMethodInterceptor(source.getClass()));
factory.addAdvice(getMethodInterceptor(source, projectionType));
return (T) factory.getProxy(getClass().getClassLoader());
}
/*
* (non-Javadoc)
* @see org.springframework.data.projection.ProjectionFactory#createProjection(java.lang.Class)
*/
@Override
public <T> T createProjection(Class<T> projectionType) {
Assert.notNull(projectionType, "Projection type must not be null!");
return createProjection(projectionType, new HashMap<String, Object>());
}
/*
* (non-Javadoc)
* @see org.springframework.data.projection.ProjectionFactory#getProperties(java.lang.Class)
*/
@Override
public List<String> getInputProperties(Class<?> projectionType) {
Assert.notNull(projectionType, "Projection type must not be null!");
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(projectionType);
List<String> result = new ArrayList<String>(descriptors.length);
for (PropertyDescriptor descriptor : descriptors) {
if (isInputProperty(descriptor)) {
result.add(descriptor.getName());
}
}
return result;
}
/**
* Returns the {@link MethodInterceptor} to add to the proxy.
*
* @param source must not be {@literal null}.
* @param projectionType must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private MethodInterceptor getMethodInterceptor(Object source, Class<?> projectionType) {
MethodInterceptor propertyInvocationInterceptor = source instanceof Map ? new MapAccessingMethodInterceptor(
(Map<String, Object>) source) : new PropertyAccessingMethodInterceptor(source);
return new ProjectingMethodInterceptor(this, postProcessAccessorInterceptor(propertyInvocationInterceptor, source,
projectionType));
}
/**
* Post-process the given {@link MethodInterceptor} for the given source instance and projection type. Default
* implementation will simply return the given interceptor.
*
* @param interceptor will never be {@literal null}.
* @param source will never be {@literal null}.
* @param projectionType will never be {@literal null}.
* @return
*/
protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor interceptor, Object source,
Class<?> projectionType) {
return interceptor;
}
/**
* Returns whether the given {@link PropertyDescriptor} describes an input property for the projection, i.e. a
* property that needs to be present on the source to be able to create reasonable projections for the type the
* descritor was looked up on.
*
* @param descriptor will never be {@literal null}.
* @return
*/
protected boolean isInputProperty(PropertyDescriptor descriptor) {
return true;
}
/**
* Extension of {@link org.springframework.aop.TargetClassAware} to be able to ignore the getter on JSON rendering.
*
* @author Oliver Gierke
*/
public static interface TargetClassAware extends org.springframework.aop.TargetClassAware {
@JsonIgnore
Class<?> getTargetClass();
}
/**
* Custom {@link MethodInterceptor} to expose the proxy target class even if we set
* {@link ProxyFactory#setOpaque(boolean)} to true to prevent properties on {@link Advised} to be rendered.
*
* @author Oliver Gierke
*/
private static class TargetClassAwareMethodInterceptor implements MethodInterceptor {
private static final Method GET_TARGET_CLASS_METHOD;
private final Class<?> targetClass;
static {
try {
GET_TARGET_CLASS_METHOD = TargetClassAware.class.getMethod("getTargetClass");
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
/**
* Creates a new {@link TargetClassAwareMethodInterceptor} with the given target class.
*
* @param targetClass must not be {@literal null}.
*/
public TargetClassAwareMethodInterceptor(Class<?> targetClass) {
Assert.notNull(targetClass, "Target class must not be null!");
this.targetClass = targetClass;
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (invocation.getMethod().equals(GET_TARGET_CLASS_METHOD)) {
return targetClass;
}
return invocation.proceed();
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2015 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.projection;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.util.AnnotationDetectionMethodCallback;
import org.springframework.util.ReflectionUtils;
/**
* A {@link ProxyProjectionFactory} that adds support to use {@link Value}-annotated methods on a projection interface
* to evaluate the contained SpEL expression to define the outcome of the method call.
*
* @author Oliver Gierke
* @since 1.10
*/
public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory implements BeanFactoryAware {
private final Map<Class<?>, Boolean> typeCache = new HashMap<Class<?>, Boolean>();
private BeanFactory beanFactory;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* Inspects the given target type for methods with {@link Value} annotations and caches the result. Will create a
* {@link SpelEvaluatingMethodInterceptor} if an annotation was found or return the delegate as is if not.
*
* @param source The backing source object.
* @param projectionType the proxy target type.
* @param delegate the root {@link MethodInterceptor}.
* @return
*/
@Override
protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor interceptor, Object source,
Class<?> projectionType) {
if (!typeCache.containsKey(projectionType)) {
AnnotationDetectionMethodCallback<Value> callback = new AnnotationDetectionMethodCallback<Value>(Value.class);
ReflectionUtils.doWithMethods(projectionType, callback);
typeCache.put(projectionType, callback.hasFoundAnnotation());
}
return typeCache.get(projectionType) ? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory)
: interceptor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.projection.ProxyProjectionFactory#isProperty(java.beans.PropertyDescriptor)
*/
@Override
protected boolean isInputProperty(PropertyDescriptor descriptor) {
Method readMethod = descriptor.getReadMethod();
if (readMethod == null) {
return false;
}
return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2014-2015 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.projection;
import java.lang.reflect.Method;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link MethodInterceptor} to invoke a SpEL expression to compute the method result. Will forward the resolution to a
* delegate {@link MethodInterceptor} if no {@link Value} annotation is found.
*
* @author Oliver Gierke
* @see 1.10
*/
class SpelEvaluatingMethodInterceptor implements MethodInterceptor {
private final SpelExpressionParser parser;
private final ParserContext parserContext;
private final EvaluationContext evaluationContext;
private final MethodInterceptor delegate;
/**
* Creates a new {@link SpelEvaluatingMethodInterceptor} delegating to the given {@link MethodInterceptor} as fallback
* and exposing the given target object via {@code target} to the SpEl expressions. If a {@link BeanFactory} is given,
* bean references in SpEl expressions can be resolved as well.
*
* @param delegate must not be {@literal null}.
* @param target must not be {@literal null}.
* @param beanFactory can be {@literal null}.
*/
public SpelEvaluatingMethodInterceptor(MethodInterceptor delegate, Object target, BeanFactory beanFactory) {
Assert.notNull(delegate, "Delegate MethodInterceptor must not be null!");
Assert.notNull(target, "TargetObject must not be null!");
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new TargetWrapper(target));
if (target instanceof Map) {
evaluationContext.addPropertyAccessor(new MapAccessor());
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
this.evaluationContext = evaluationContext;
this.parser = new SpelExpressionParser();
this.parserContext = new TemplateParserContext();
this.delegate = delegate;
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Value annotation = method.getAnnotation(Value.class);
if (annotation == null) {
return delegate.invoke(invocation);
}
if (!StringUtils.hasText(annotation.value())) {
throw new IllegalStateException(String.format("@Value annotation on %s contains empty expression!", method));
}
Expression expression = parser.parseExpression(annotation.value(), parserContext);
return expression.getValue(evaluationContext);
}
/**
* Wrapper class to expose an object to the SpEL expression as {@code target}.
*
* @author Oliver Gierke
*/
static class TargetWrapper {
private final Object target;
public TargetWrapper(Object target) {
this.target = target;
}
/**
* @return the target
*/
public Object getTarget() {
return target;
}
}
}