DATACMNS-1158 - ProjectionFactory is now configurable by module implementations.

Introduced RepositoryFactorySupport.getProjectionFactory(…) to create a ProjectionFactory to be used for repository instances created. The default implementation uses the SpelAwareProxyProjectionFactory.

The ProjectionInformation implementation is now a named class so it can be used for more specialized implementations.

Original pull request: #243.
Related issue: DATAJPA-1173.
This commit is contained in:
Oliver Gierke
2017-11-24 11:15:57 +01:00
parent e79d70cb13
commit 3765feb917
3 changed files with 56 additions and 31 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2017 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.
@@ -77,7 +77,8 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl
}
return typeCache.get(projectionType)
? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory, parser, projectionType) : interceptor;
? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory, parser, projectionType)
: interceptor;
}
/*
@@ -86,28 +87,33 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl
*/
@Override
public ProjectionInformation getProjectionInformation(Class<?> projectionType) {
return new SpelAwareProjectionInformation(projectionType);
}
return new DefaultProjectionInformation(projectionType) {
protected static class SpelAwareProjectionInformation extends DefaultProjectionInformation {
/*
* (non-Javadoc)
* @see org.springframework.data.projection.DefaultProjectionInformation#isInputProperty(java.beans.PropertyDescriptor)
*/
@Override
protected boolean isInputProperty(PropertyDescriptor descriptor) {
protected SpelAwareProjectionInformation(Class<?> projectionType) {
super(projectionType);
}
if (!super.isInputProperty(descriptor)) {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.projection.DefaultProjectionInformation#isInputProperty(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;
if (!super.isInputProperty(descriptor)) {
return false;
}
};
Method readMethod = descriptor.getReadMethod();
if (readMethod == null) {
return false;
}
return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2015 the original author or authors.
* Copyright 2008-2017 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.
@@ -37,6 +37,7 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.EntityInformation;
@@ -218,11 +219,29 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target, projectionFactory));
return (T) result.getProxy(classLoader);
}
/**
* Returns the {@link ProjectionFactory} to be used with the repository instances created.
*
* @param classLoader will never be {@literal null}.
* @param beanFactory will never be {@literal null}.
* @return will never be {@literal null}.
*/
protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
factory.setBeanClassLoader(classLoader);
factory.setBeanFactory(beanFactory);
return factory;
}
/**
* Returns the {@link RepositoryMetadata} for the given repository interface.
*
@@ -401,7 +420,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
* execution of repository interface methods.
*/
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, Object customImplementation,
Object target) {
Object target, ProjectionFactory projectionFactory) {
Assert.notNull(repositoryInformation, "RepositoryInformation must not be null!");
Assert.notNull(target, "Target must not be null!");
@@ -427,13 +446,10 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
return;
}
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
factory.setBeanClassLoader(classLoader);
factory.setBeanFactory(beanFactory);
for (Method method : queryMethods) {
RepositoryQuery query = lookupStrategy.resolveQuery(method, repositoryInformation, factory, namedQueries);
RepositoryQuery query = lookupStrategy.resolveQuery(method, repositoryInformation, projectionFactory,
namedQueries);
invokeListeners(query);
queries.put(method, query);
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2017 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.
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -45,13 +46,15 @@ public class QueryExecutorMethodInterceptorUnitTests {
@Mock RepositoryInformation information;
@Mock QueryLookupStrategy strategy;
ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
@Test(expected = IllegalStateException.class)
public void rejectsRepositoryInterfaceWithQueryMethodsIfNoQueryLookupStrategyIsDefined() throws Exception {
when(information.getQueryMethods()).thenReturn(Arrays.asList(Object.class.getMethod("toString")));
when(factory.getQueryLookupStrategy(any(Key.class))).thenReturn(null);
factory.new QueryExecutorMethodInterceptor(information, null, new Object());
factory.new QueryExecutorMethodInterceptor(information, null, new Object(), projectionFactory);
}
@Test
@@ -60,7 +63,7 @@ public class QueryExecutorMethodInterceptorUnitTests {
when(information.getQueryMethods()).thenReturn(Collections.<Method> emptySet());
when(factory.getQueryLookupStrategy(any(Key.class))).thenReturn(strategy);
factory.new QueryExecutorMethodInterceptor(information, null, new Object());
factory.new QueryExecutorMethodInterceptor(information, null, new Object(), projectionFactory);
verify(strategy, times(0)).resolveQuery(any(Method.class), any(RepositoryMetadata.class),
any(ProjectionFactory.class), any(NamedQueries.class));
}