Browse Source

DATACMNS-1102 - Reuse ConversionService in ProjectingMethodInterceptors created by ProxyProjectionFactory.

Applied the same to internals of MapDataBinder.
pull/231/head
Oliver Gierke 9 years ago
parent
commit
f44255b9a8
  1. 29
      src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java
  2. 7
      src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java
  3. 34
      src/main/java/org/springframework/data/web/MapDataBinder.java
  4. 30
      src/test/java/org/springframework/data/projection/ProjectingMethodInterceptorUnitTests.java
  5. 1
      src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java
  6. 2
      src/test/java/org/springframework/data/web/MapDataBinderUnitTests.java

29
src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java

@ -15,6 +15,9 @@
*/ */
package org.springframework.data.projection; package org.springframework.data.projection;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -27,7 +30,6 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -41,29 +43,12 @@ import org.springframework.util.ObjectUtils;
* @author Oliver Gierke * @author Oliver Gierke
* @since 1.10 * @since 1.10
*/ */
@RequiredArgsConstructor
class ProjectingMethodInterceptor implements MethodInterceptor { class ProjectingMethodInterceptor implements MethodInterceptor {
private final ProjectionFactory factory; private final @NonNull ProjectionFactory factory;
private final MethodInterceptor delegate; private final @NonNull MethodInterceptor delegate;
private final ConversionService conversionService; private final @NonNull ConversionService conversionService;
/**
* 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;
this.conversionService = new DefaultConversionService();
}
/* /*
* (non-Javadoc) * (non-Javadoc)

7
src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java

@ -28,6 +28,8 @@ import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -49,6 +51,7 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware,
ProxyProjectionFactory.class.getClassLoader()); ProxyProjectionFactory.class.getClassLoader());
private final List<MethodInterceptorFactory> factories; private final List<MethodInterceptorFactory> factories;
private final ConversionService conversionService;
private ClassLoader classLoader; private ClassLoader classLoader;
/** /**
@ -59,6 +62,8 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware,
this.factories = new ArrayList<>(); this.factories = new ArrayList<>();
this.factories.add(MapAccessingMethodInterceptorFactory.INSTANCE); this.factories.add(MapAccessingMethodInterceptorFactory.INSTANCE);
this.factories.add(PropertyAccessingMethodInvokerFactory.INSTANCE); this.factories.add(PropertyAccessingMethodInvokerFactory.INSTANCE);
this.conversionService = new DefaultConversionService();
} }
/** /**
@ -176,7 +181,7 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware,
.createMethodInterceptor(source, projectionType); .createMethodInterceptor(source, projectionType);
return new ProjectingMethodInterceptor(this, return new ProjectingMethodInterceptor(this,
postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType)); postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType), conversionService);
} }
/** /**

34
src/main/java/org/springframework/data/web/MapDataBinder.java

@ -15,6 +15,9 @@
*/ */
package org.springframework.data.web; package org.springframework.data.web;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -31,7 +34,6 @@ import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
@ -97,32 +99,15 @@ class MapDataBinder extends WebDataBinder {
* @author Oliver Gierke * @author Oliver Gierke
* @since 1.10 * @since 1.10
*/ */
@RequiredArgsConstructor
private static class MapPropertyAccessor extends AbstractPropertyAccessor { private static class MapPropertyAccessor extends AbstractPropertyAccessor {
private static final SpelExpressionParser PARSER = new SpelExpressionParser( private static final SpelExpressionParser PARSER = new SpelExpressionParser(
new SpelParserConfiguration(false, true)); new SpelParserConfiguration(false, true));
private final Class<?> type; private final @NonNull Class<?> type;
private final Map<String, Object> map; private final @NonNull Map<String, Object> map;
private final ConversionService conversionService; private final @NonNull ConversionService conversionService;
/**
* Creates a new {@link MapPropertyAccessor} for the given type, map and {@link ConversionService}.
*
* @param type must not be {@literal null}.
* @param map must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public MapPropertyAccessor(Class<?> type, Map<String, Object> map, ConversionService conversionService) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(map, "Map must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.type = type;
this.map = map;
this.conversionService = conversionService;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
@ -177,7 +162,7 @@ class MapDataBinder extends WebDataBinder {
} }
StandardEvaluationContext context = new StandardEvaluationContext(); StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new PropertyTraversingMapAccessor(type, new DefaultConversionService())); context.addPropertyAccessor(new PropertyTraversingMapAccessor(type, conversionService));
context.setTypeConverter(new StandardTypeConverter(conversionService)); context.setTypeConverter(new StandardTypeConverter(conversionService));
context.setRootObject(map); context.setRootObject(map);
@ -290,7 +275,8 @@ class MapDataBinder extends WebDataBinder {
Class<?> actualPropertyType = path.getType(); Class<?> actualPropertyType = path.getType();
TypeDescriptor valueDescriptor = conversionService.canConvert(String.class, actualPropertyType) TypeDescriptor valueDescriptor = conversionService.canConvert(String.class, actualPropertyType)
? TypeDescriptor.valueOf(String.class) : TypeDescriptor.valueOf(HashMap.class); ? TypeDescriptor.valueOf(String.class)
: TypeDescriptor.valueOf(HashMap.class);
return path.isCollection() ? TypeDescriptor.collection(emptyValue.getClass(), valueDescriptor) return path.isCollection() ? TypeDescriptor.collection(emptyValue.getClass(), valueDescriptor)
: TypeDescriptor.map(emptyValue.getClass(), TypeDescriptor.valueOf(String.class), valueDescriptor); : TypeDescriptor.map(emptyValue.getClass(), TypeDescriptor.valueOf(String.class), valueDescriptor);

30
src/test/java/org/springframework/data/projection/ProjectingMethodInterceptorUnitTests.java

@ -16,6 +16,7 @@
package org.springframework.data.projection; package org.springframework.data.projection;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import java.util.Collection; import java.util.Collection;
@ -30,6 +31,8 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
/** /**
* Unit tests for {@link ProjectingMethodInterceptor}. * Unit tests for {@link ProjectingMethodInterceptor}.
@ -43,11 +46,13 @@ public class ProjectingMethodInterceptorUnitTests {
@Mock MethodInterceptor interceptor; @Mock MethodInterceptor interceptor;
@Mock MethodInvocation invocation; @Mock MethodInvocation invocation;
@Mock ProjectionFactory factory; @Mock ProjectionFactory factory;
ConversionService conversionService = new DefaultConversionService();
@Test // DATAREST-221 @Test // DATAREST-221
public void wrapsDelegateResultInProxyIfTypesDontMatch() throws Throwable { public void wrapsDelegateResultInProxyIfTypesDontMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getHelper")); when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getHelper"));
when(interceptor.invoke(invocation)).thenReturn("Foo"); when(interceptor.invoke(invocation)).thenReturn("Foo");
@ -58,7 +63,7 @@ public class ProjectingMethodInterceptorUnitTests {
@Test // DATAREST-221 @Test // DATAREST-221
public void retunsDelegateResultAsIsIfTypesMatch() throws Throwable { public void retunsDelegateResultAsIsIfTypesMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor, conversionService);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getString")); when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getString"));
when(interceptor.invoke(invocation)).thenReturn("Foo"); when(interceptor.invoke(invocation)).thenReturn("Foo");
@ -69,7 +74,7 @@ public class ProjectingMethodInterceptorUnitTests {
@Test // DATAREST-221 @Test // DATAREST-221
public void returnsNullAsIs() throws Throwable { public void returnsNullAsIs() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor, conversionService);
when(interceptor.invoke(invocation)).thenReturn(null); when(interceptor.invoke(invocation)).thenReturn(null);
@ -79,20 +84,21 @@ public class ProjectingMethodInterceptorUnitTests {
@Test // DATAREST-221 @Test // DATAREST-221
public void considersPrimitivesAsWrappers() throws Throwable { public void considersPrimitivesAsWrappers() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor, conversionService);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getPrimitive")); when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getPrimitive"));
when(interceptor.invoke(invocation)).thenReturn(1L); when(interceptor.invoke(invocation)).thenReturn(1L);
assertThat(methodInterceptor.invoke(invocation)).isEqualTo(1L); assertThat(methodInterceptor.invoke(invocation)).isEqualTo(1L);
verify(factory, times(0)).createProjection((Class<?>) anyObject(), anyObject()); verify(factory, times(0)).createProjection((Class<?>) any(), any());
} }
@Test // DATAREST-394, DATAREST-408 @Test // DATAREST-394, DATAREST-408
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptySets() throws Throwable { public void appliesProjectionToNonEmptySets() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
Object result = methodInterceptor Object result = methodInterceptor
.invoke(mockInvocationOf("getHelperCollection", Collections.singleton(mock(Helper.class)))); .invoke(mockInvocationOf("getHelperCollection", Collections.singleton(mock(Helper.class))));
@ -107,7 +113,8 @@ public class ProjectingMethodInterceptorUnitTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyLists() throws Throwable { public void appliesProjectionToNonEmptyLists() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
Object result = methodInterceptor Object result = methodInterceptor
.invoke(mockInvocationOf("getHelperList", Collections.singletonList(mock(Helper.class)))); .invoke(mockInvocationOf("getHelperList", Collections.singletonList(mock(Helper.class))));
@ -123,7 +130,8 @@ public class ProjectingMethodInterceptorUnitTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void allowsMaskingAnArrayIntoACollection() throws Throwable { public void allowsMaskingAnArrayIntoACollection() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperArray", new Helper[] { mock(Helper.class) })); Object result = methodInterceptor.invoke(mockInvocationOf("getHelperArray", new Helper[] { mock(Helper.class) }));
assertThat(result).isInstanceOf(Collection.class); assertThat(result).isInstanceOf(Collection.class);
@ -138,7 +146,8 @@ public class ProjectingMethodInterceptorUnitTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyMap() throws Throwable { public void appliesProjectionToNonEmptyMap() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
Object result = methodInterceptor Object result = methodInterceptor
.invoke(mockInvocationOf("getHelperMap", Collections.singletonMap("foo", mock(Helper.class)))); .invoke(mockInvocationOf("getHelperMap", Collections.singletonMap("foo", mock(Helper.class))));
@ -154,7 +163,8 @@ public class ProjectingMethodInterceptorUnitTests {
@Test @Test
public void returnsSingleElementCollectionForTargetThatReturnsNonCollection() throws Throwable { public void returnsSingleElementCollectionForTargetThatReturnsNonCollection() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor); MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
conversionService);
Helper reference = mock(Helper.class); Helper reference = mock(Helper.class);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperCollection", reference)); Object result = methodInterceptor.invoke(mockInvocationOf("getHelperCollection", reference));

1
src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java

@ -77,7 +77,6 @@ public class ProxyProjectionFactoryUnitTests {
} }
@Test // DATAREST-221, DATACMNS-630 @Test // DATAREST-221, DATACMNS-630
@SuppressWarnings("rawtypes")
public void proxyExposesTargetClassAware() { public void proxyExposesTargetClassAware() {
CustomerExcerpt proxy = factory.createProjection(CustomerExcerpt.class); CustomerExcerpt proxy = factory.createProjection(CustomerExcerpt.class);

2
src/test/java/org/springframework/data/web/MapDataBinderUnitTests.java

@ -56,7 +56,6 @@ public class MapDataBinderUnitTests {
} }
@Test // DATACMNS-630 @Test // DATACMNS-630
@SuppressWarnings("rawtypes")
public void bindsNestedCollectionElement() { public void bindsNestedCollectionElement() {
MutablePropertyValues values = new MutablePropertyValues(); MutablePropertyValues values = new MutablePropertyValues();
@ -71,7 +70,6 @@ public class MapDataBinderUnitTests {
} }
@Test // DATACMNS-630 @Test // DATACMNS-630
@SuppressWarnings("rawtypes")
public void bindsNestedPrimitive() { public void bindsNestedPrimitive() {
MutablePropertyValues values = new MutablePropertyValues(); MutablePropertyValues values = new MutablePropertyValues();

Loading…
Cancel
Save