DATAREST-437 - Moved to projections API of Spring Data Commons.

Related tickets: DATACMNS-630, DATACMNS-618.
This commit is contained in:
Oliver Gierke
2015-01-11 16:45:41 +01:00
parent af745cfb37
commit f09880ff16
15 changed files with 25 additions and 1075 deletions

View File

@@ -1,145 +0,0 @@
/*
* 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.rest.core.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
*/
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.
* @param delegate the {@link MethodInterceptor} to trigger to create the source value.
*/
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(result,
returnType);
}
/**
* Turns the given value into a {@link Collection}. Will create an empty {@link Collection} for {@literal null}, turn
* an array iinto a collection an wrap all other values into a single-element collection.
*
* @param source can be {@literal null}.
* @return
*/
private static Collection<?> asCollection(Object source) {
if (source == null) {
return Collections.emptySet();
} else if (source instanceof Collection) {
return (Collection<?>) source;
} else if (source.getClass().isArray()) {
return Arrays.asList((Object[]) source);
} else {
return Collections.singleton(source);
}
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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.rest.core.projection;
/**
* 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
*/
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 source the object to create a projection for, can be {@literal null}
* @param projectionType the type to create.
* @return
*/
<T> T createProjection(Object source, Class<T> projectionType);
}

View File

@@ -1,70 +0,0 @@
/*
* 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.rest.core.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
*/
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

@@ -1,181 +0,0 @@
/*
* 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.rest.core.projection;
import java.lang.reflect.Method;
import java.util.HashMap;
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.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.util.AnnotationDetectionMethodCallback;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* A {@link ProjectionFactory} to create JDK proxies to back interfaces and handle method invocations on them. By
* default two different kinds of methods are supported:
* <ol>
* <li>Bean property accessor methods - invocations will be delegated into a property lookup on the target instance.</li>
* <li>Arbitrary methods annotated with {@link Value} to contain a SpEL expression, which will be evaluated on
* invocation. The expressions can use {@code target} to refer to the proxy target.</li>
* </ol>
* In case the dlegating 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
*/
public class ProxyProjectionFactory implements ProjectionFactory {
private final Map<Class<?>, Boolean> typeCache = new HashMap<Class<?>, Boolean>();
private BeanFactory beanFactory;
/**
* Creates a new {@link ProxyProjectionFactory} using the given {@link BeanFactory}.
*
* @param beanFactory can be {@literal null}. If {@literal null}, SpEL expressions at projection interfaces cannot use
* bean references.
*/
public ProxyProjectionFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.projection.ProjectionFactory#createProjection(java.lang.Object, java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T> T createProjection(Object source, Class<T> projectionType) {
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();
}
/**
* Returns the {@link MethodInterceptor} to add to the proxy.
*
* @param source must not be {@literal null}.
* @param projectionType must not be {@literal null}.
* @return
*/
private MethodInterceptor getMethodInterceptor(Object source, Class<?> projectionType) {
MethodInterceptor propertyInvocationInterceptor = new PropertyAccessingMethodInterceptor(source);
return new ProjectingMethodInterceptor(this, getSpelMethodInterceptorIfNecessary(source, projectionType,
propertyInvocationInterceptor));
}
/**
* 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
*/
private MethodInterceptor getSpelMethodInterceptorIfNecessary(Object source, Class<?> projectionType,
MethodInterceptor delegate) {
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(delegate, source, beanFactory)
: delegate;
}
/**
* 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

@@ -1,111 +0,0 @@
/*
* 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.rest.core.projection;
import java.lang.reflect.Method;
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.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
*/
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 (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 || !StringUtils.hasText(annotation.value())) {
return delegate.invoke(invocation);
}
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;
}
}
}

View File

@@ -1,221 +0,0 @@
/*
* 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.rest.core.projection;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link ProjectingMethodInterceptor}.
*
* @author Oliver Gierke
* @author Saulo Medeiros de Araujo
*/
@RunWith(MockitoJUnitRunner.class)
public class ProjectingMethodInterceptorUnitTests {
@Mock MethodInterceptor interceptor;
@Mock MethodInvocation invocation;
@Mock ProjectionFactory factory;
/**
* @see DATAREST-221
*/
@Test
public void wrapsDelegateResultInProxyIfTypesDontMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(null), interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getHelper"));
when(interceptor.invoke(invocation)).thenReturn("Foo");
assertThat(methodInterceptor.invoke(invocation), is(instanceOf(Helper.class)));
}
/**
* @see DATAREST-221
*/
@Test
public void retunsDelegateResultAsIsIfTypesMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getString"));
when(interceptor.invoke(invocation)).thenReturn("Foo");
assertThat(methodInterceptor.invoke(invocation), is((Object) "Foo"));
}
/**
* @see DATAREST-221
*/
@Test
public void returnsNullAsIs() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(interceptor.invoke(invocation)).thenReturn(null);
assertThat(methodInterceptor.invoke(invocation), is(nullValue()));
}
/**
* @see DATAREST-221
*/
@Test
public void considersPrimitivesAsWrappers() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getPrimitive"));
when(interceptor.invoke(invocation)).thenReturn(1L);
assertThat(methodInterceptor.invoke(invocation), is((Object) 1L));
verify(factory, times(0)).createProjection(anyObject(), (Class<?>) anyObject());
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptySets() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(null), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperCollection",
Collections.singleton(mock(Helper.class))));
assertThat(result, is(instanceOf(Set.class)));
Set<Object> projections = (Set<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyLists() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(null), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperList",
Collections.singletonList(mock(Helper.class))));
assertThat(result, is(instanceOf(List.class)));
List<Object> projections = (List<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void allowsMaskingAnArrayIntoACollection() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(null), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperArray", new Helper[] { mock(Helper.class) }));
assertThat(result, is(instanceOf(Collection.class)));
Collection<Object> projections = (Collection<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyMap() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(null), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperMap",
Collections.singletonMap("foo", mock(Helper.class))));
assertThat(result, is(instanceOf(Map.class)));
Map<String, Object> projections = (Map<String, Object>) result;
assertThat(projections.entrySet(), is(Matchers.<Entry<String, Object>> iterableWithSize(1)));
assertThat(projections, hasEntry(is("foo"), instanceOf(HelperProjection.class)));
}
/**
* Mocks the {@link Helper} method of the given name to return the given value.
*
* @param methodName
* @param returnValue
* @return
* @throws Throwable
*/
private MethodInvocation mockInvocationOf(String methodName, Object returnValue) throws Throwable {
when(invocation.getMethod()).thenReturn(Helper.class.getMethod(methodName));
when(interceptor.invoke(invocation)).thenReturn(returnValue);
return invocation;
}
interface Helper {
Helper getHelper();
String getString();
long getPrimitive();
Collection<HelperProjection> getHelperCollection();
List<HelperProjection> getHelperList();
Set<HelperProjection> getHelperSet();
Map<String, HelperProjection> getHelperMap();
Collection<HelperProjection> getHelperArray();
}
interface HelperProjection {
Helper getHelper();
String getString();
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.rest.core.projection;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.NotReadablePropertyException;
/**
* Unit tests for {@link PropertyAccessingMethodInterceptor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class PropertyAccessingMethodInterceptorUnitTests {
@Mock MethodInvocation invocation;
/**
* @see DATAREST-221
*/
@Test
public void triggersPropertyAccessOnTarget() throws Throwable {
Source source = new Source();
source.firstname = "Dave";
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getFirstname"));
MethodInterceptor interceptor = new PropertyAccessingMethodInterceptor(source);
assertThat(interceptor.invoke(invocation), is((Object) "Dave"));
}
/**
* @see DATAREST-221
*/
@Test(expected = NotReadablePropertyException.class)
public void throwsAppropriateExceptionIfThePropertyCannotBeFound() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getLastname"));
new PropertyAccessingMethodInterceptor(new Source()).invoke(invocation);
}
/**
* @see DATAREST-221
*/
@Test
public void forwardsObjectMethodInvocation() throws Throwable {
when(invocation.getMethod()).thenReturn(Object.class.getMethod("toString"));
new PropertyAccessingMethodInterceptor(new Source()).invoke(invocation);
}
static class Source {
String firstname;
}
interface Projection {
String getFirstname();
String getLastname();
}
}

View File

@@ -1,110 +0,0 @@
/*
* 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.rest.core.projection;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.aop.TargetClassAware;
import org.springframework.beans.factory.annotation.Value;
/**
* Unit tests for {@link ProxyProjectionFactory}.
*
* @author Oliver Gierke
*/
public class ProxyProjectionFactoryUnitTests {
ProjectionFactory factory = new ProxyProjectionFactory(null);
/**
* @see DATAREST-221
*/
@Test
public void createsProjectingProxy() {
Customer customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
customer.address = new Address();
customer.address.city = "New York";
customer.address.zipCode = "ZIP";
CustomerExcerpt excerpt = factory.createProjection(customer, CustomerExcerpt.class);
assertThat(excerpt, is(instanceOf(TargetClassAware.class)));
assertThat(excerpt.getFirstname(), is("Dave"));
assertThat(excerpt.getAddress().getZipCode(), is("ZIP"));
}
/**
* @see DATAREST-221
*/
@Test
public void proxyExposesTargetClassAware() {
assertThat(factory.createProjection(new Object(), CustomerExcerpt.class), is(instanceOf(TargetClassAware.class)));
}
/**
* @see DATAREST-221
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNonInterfacesAsProjectionTarget() {
factory.createProjection(new Object(), Object.class);
}
/**
* @see DATAREST-221
*/
@Test
public void exposesSpelInvokingMethod() {
Customer customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
CustomerExcerpt excerpt = factory.createProjection(customer, CustomerExcerpt.class);
assertThat(excerpt.getFullName(), is("Dave Matthews"));
}
static class Customer {
public String firstname, lastname;
public Address address;
}
static class Address {
public String zipCode, city;
}
interface CustomerExcerpt {
String getFirstname();
AddressExcerpt getAddress();
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}
interface AddressExcerpt {
String getZipCode();
}
}

View File

@@ -1,93 +0,0 @@
/*
* 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.rest.core.projection;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* Unit tests for {@link SpelEvaluatingMethodInterceptor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class SpelEvaluatingMethodInterceptorUnitTests {
@Mock MethodInterceptor delegate;
@Mock MethodInvocation invocation;
/**
* @see DATAREST-221
*/
@Test
public void invokesMethodOnTarget() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("propertyFromTarget"));
MethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(), null);
assertThat(interceptor.invoke(invocation), is((Object) "property"));
}
/**
* @see DATAREST-221
*/
@Test
public void invokesMethodOnBean() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("invokeBean"));
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("someBean", new SomeBean());
SpelEvaluatingMethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(), factory);
assertThat(interceptor.invoke(invocation), is((Object) "value"));
}
interface Projection {
@Value("#{target.property}")
String propertyFromTarget();
@Value("#{@someBean.value}")
String invokeBean();
}
static class Target {
public String getProperty() {
return "property";
}
}
static class SomeBean {
public String getValue() {
return "value";
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* 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.
@@ -16,10 +16,10 @@
package org.springframework.data.rest.webmvc.config;
import org.springframework.core.MethodParameter;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
import org.springframework.data.rest.core.projection.ProjectionFactory;
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
import org.springframework.data.rest.webmvc.support.PersistentEntityProjector;
import org.springframework.hateoas.EntityLinks;

View File

@@ -46,6 +46,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.geo.GeoModule;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
@@ -59,7 +60,6 @@ import org.springframework.data.rest.core.event.ValidatingRepositoryEventListene
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
import org.springframework.data.rest.core.support.DomainObjectMerger;
import org.springframework.data.rest.core.support.RepositoryRelProvider;
import org.springframework.data.rest.webmvc.BasePathAwareController;
@@ -624,9 +624,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
projectionFactory.setBeanFactory(applicationContext);
PersistentEntityResourceAssemblerArgumentResolver peraResolver = new PersistentEntityResourceAssemblerArgumentResolver(
repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(
applicationContext), resourceMappings());
repositories(), entityLinks(), config().projectionConfiguration(), projectionFactory, resourceMappings());
HateoasPageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
HandlerMethodArgumentResolver defaultedPageableResolver = new DefaultedPageableHandlerMethodArgumentResolver(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* 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.
@@ -15,10 +15,10 @@
*/
package org.springframework.data.rest.webmvc.support;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
import org.springframework.data.rest.core.projection.ProjectionFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -67,7 +67,7 @@ public class PersistentEntityProjector implements Projector {
}
Class<?> projectionType = projectionDefinitions.getProjectionType(source.getClass(), projection);
return projectionType == null ? source : factory.createProjection(source, projectionType);
return projectionType == null ? source : factory.createProjection(projectionType, source);
}
/*
@@ -86,7 +86,7 @@ public class PersistentEntityProjector implements Projector {
return project(source);
}
return projection == null ? source : factory.createProjection(source, projection);
return projection == null ? source : factory.createProjection(projection, source);
}
/*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-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.
@@ -36,9 +36,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.ResourcesProcessorWrapper;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
@@ -233,8 +233,8 @@ public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests {
ProjectionProcessor projectionProcessor = new ProjectionProcessor();
resourceProcessors.add(projectionProcessor);
ProxyProjectionFactory factory = new ProxyProjectionFactory(new DefaultListableBeanFactory());
SampleProjection projection = factory.createProjection(new Sample(), SampleProjection.class);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
SampleProjection projection = factory.createProjection(SampleProjection.class, new Sample());
Resource<SampleProjection> resource = new Resource<SampleProjection>(projection);
invokeReturnValueHandler("object", is(resource), resource);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* 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.
@@ -22,8 +22,8 @@ import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.rest.core.projection.ProjectionFactory;
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.hateoas.hal.Jackson2HalModule;
@@ -41,7 +41,7 @@ import com.jayway.jsonpath.JsonPath;
public class ProjectionJacksonIntegrationTests {
ObjectMapper mapper;
ProjectionFactory factory = new ProxyProjectionFactory(null);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
@Before
public void setUp() {
@@ -62,7 +62,7 @@ public class ProjectionJacksonIntegrationTests {
customer.lastname = "Matthews";
customer.address = new Address();
CustomerProjection projection = factory.createProjection(customer, CustomerProjection.class);
CustomerProjection projection = factory.createProjection(CustomerProjection.class, customer);
String result = mapper.writeValueAsString(projection);
assertThat(JsonPath.read(result, "$firstname"), is((Object) "Dave"));
@@ -83,7 +83,7 @@ public class ProjectionJacksonIntegrationTests {
customer.lastname = "Matthews";
customer.address = new Address();
CustomerProjection projection = factory.createProjection(customer, CustomerProjection.class);
CustomerProjection projection = factory.createProjection(CustomerProjection.class, customer);
Resources<CustomerProjection> resources = new Resources<CustomerProjection>(Arrays.asList(projection));
String result = mapper.writeValueAsString(resources);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* 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.
@@ -24,9 +24,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.projection.ProjectionFactory;
/**
* Unit tests for {@link PersistentEntityProjector}.
@@ -69,7 +69,7 @@ public class PersistentEntityProjectorUnitTests {
Object source = new Object();
projector.project(source);
verify(factory, times(1)).createProjection(source, Sample.class);
verify(factory, times(1)).createProjection(Sample.class, source);
}
interface Sample {