DATACMNS-89 - Infrastructure for projections on repository queries.
QueryMethods now expose a ResourceProcessor which is exposes information about the final to be created object types which can either be DTOs containing a persistence constructor (see @PersistenceConstructor) or projection interfaces. The former are analyzed for constructor properties so that store implementations can use that information to create projected queries that return exactly the fields required for that DTO. Projection interfaces are inspected, their properties are considered input properties and the same projection queries can be issued against the data store. If a projection contains dynamically calculated properties (i.e. it uses SpEL expressions via @Value) the original entities have to be queried and can be projected during post processing. ProjectionFactory now exposes a more advanced ProjectionInformation that has additional meta information about the projection type. ProxyProjectionFactory now refers to the BeanClassLoader instead of the ResourceLoader. RepositoryFactory(Bean)Support now also implement BeanFactoryAware to forward the BeanFactory to the SpelAwareProxyProjectionFactory which in turn now gets handed into the QueryLookupStrategy as well as the QueryMethod. Parameter now knows about a dynamic projection type, which is a query method parameter of type Class bound to a generic method parameter and will be used to determine the projection to be used on a per call basis. Original pull request: #150.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ProjectionInformation}. Exposes all properties of the type as required input
|
||||
* properties.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
class DefaultProjectionInformation implements ProjectionInformation {
|
||||
|
||||
private final Class<?> projectionType;
|
||||
private final List<PropertyDescriptor> properties;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultProjectionInformation} for the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public DefaultProjectionInformation(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Projection type must not be null!");
|
||||
|
||||
this.projectionType = type;
|
||||
this.properties = collectDescriptors(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.ProjectionInformation#getType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return projectionType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.ProjectionInformation#getInputProperties()
|
||||
*/
|
||||
public List<PropertyDescriptor> getInputProperties() {
|
||||
|
||||
List<PropertyDescriptor> result = new ArrayList<PropertyDescriptor>();
|
||||
|
||||
for (PropertyDescriptor descriptor : properties) {
|
||||
if (isInputProperty(descriptor)) {
|
||||
result.add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.ProjectionInformation#isDynamic()
|
||||
*/
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return this.properties.equals(getInputProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* descriptor was looked up on.
|
||||
*
|
||||
* @param descriptor will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects {@link PropertyDescriptor}s for all properties exposed by the given type and all its super interfaces.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static List<PropertyDescriptor> collectDescriptors(Class<?> type) {
|
||||
|
||||
List<PropertyDescriptor> result = new ArrayList<PropertyDescriptor>();
|
||||
result.addAll(Arrays.asList(BeanUtils.getPropertyDescriptors(type)));
|
||||
|
||||
for (Class<?> interfaze : type.getInterfaces()) {
|
||||
result.addAll(collectDescriptors(interfaze));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,17 @@ public interface ProjectionFactory {
|
||||
*
|
||||
* @param projectionType must not be {@literal null}.
|
||||
* @return
|
||||
* @deprecated use {@link #getProjectionInformation(Class)}
|
||||
*/
|
||||
@Deprecated
|
||||
List<String> getInputProperties(Class<?> projectionType);
|
||||
|
||||
/**
|
||||
* Returns the {@link ProjectionInformation} for the given projection type.
|
||||
*
|
||||
* @param projectionType must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.12
|
||||
*/
|
||||
ProjectionInformation getProjectionInformation(Class<?> projectionType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
/**
|
||||
* Information about a projection type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
public interface ProjectionInformation {
|
||||
|
||||
/**
|
||||
* Returns the projection type.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
Class<?> getType();
|
||||
|
||||
/**
|
||||
* Returns the properties that will be consumed by the projection type.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
List<PropertyDescriptor> getInputProperties();
|
||||
|
||||
/**
|
||||
* Returns whether supplying values for the properties returned via {@link #getInputProperties()} is sufficient to
|
||||
* create a working proxy instance. This will usually be used to determine whether the projection uses any dynamically
|
||||
* resolved properties.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isClosed();
|
||||
}
|
||||
@@ -26,7 +26,7 @@ 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.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -42,20 +42,30 @@ import org.springframework.util.ClassUtils;
|
||||
* @see SpelAwareProxyProjectionFactory
|
||||
* @since 1.10
|
||||
*/
|
||||
class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware {
|
||||
class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware, BeanClassLoaderAware {
|
||||
|
||||
private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional",
|
||||
ProxyProjectionFactory.class.getClassLoader());
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
* @deprecated rather set the {@link ClassLoader} directly via {@link #setBeanClassLoader(ClassLoader)}.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.classLoader = resourceLoader.getClassLoader();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
|
||||
*/
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -85,8 +95,7 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware {
|
||||
factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
|
||||
factory.addAdvice(getMethodInterceptor(source, projectionType));
|
||||
|
||||
return (T) factory
|
||||
.getProxy(resourceLoader == null ? ClassUtils.getDefaultClassLoader() : resourceLoader.getClassLoader());
|
||||
return (T) factory.getProxy(classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -110,18 +119,24 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware {
|
||||
|
||||
Assert.notNull(projectionType, "Projection type must not be null!");
|
||||
|
||||
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(projectionType);
|
||||
List<String> result = new ArrayList<String>(descriptors.length);
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
if (isInputProperty(descriptor)) {
|
||||
result.add(descriptor.getName());
|
||||
}
|
||||
for (PropertyDescriptor descriptor : getProjectionInformation(projectionType).getInputProperties()) {
|
||||
result.add(descriptor.getName());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.ProjectionFactory#getProjectionInformation(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ProjectionInformation getProjectionInformation(Class<?> projectionType) {
|
||||
return new DefaultProjectionInformation(projectionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodInterceptor} to add to the proxy.
|
||||
*
|
||||
@@ -132,11 +147,12 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware {
|
||||
@SuppressWarnings("unchecked")
|
||||
private MethodInterceptor getMethodInterceptor(Object source, Class<?> projectionType) {
|
||||
|
||||
MethodInterceptor propertyInvocationInterceptor = source instanceof Map ? new MapAccessingMethodInterceptor(
|
||||
(Map<String, Object>) source) : new PropertyAccessingMethodInterceptor(source);
|
||||
MethodInterceptor propertyInvocationInterceptor = source instanceof Map
|
||||
? new MapAccessingMethodInterceptor((Map<String, Object>) source)
|
||||
: new PropertyAccessingMethodInterceptor(source);
|
||||
|
||||
return new ProjectingMethodInterceptor(this, postProcessAccessorInterceptor(propertyInvocationInterceptor, source,
|
||||
projectionType));
|
||||
return new ProjectingMethodInterceptor(this,
|
||||
postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,18 +169,6 @@ class ProxyProjectionFactory implements ProjectionFactory, ResourceLoaderAware {
|
||||
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
|
||||
* descriptor was looked up on.
|
||||
*
|
||||
* @param descriptor will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -75,23 +75,34 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl
|
||||
typeCache.put(projectionType, callback.hasFoundAnnotation());
|
||||
}
|
||||
|
||||
return typeCache.get(projectionType) ? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory,
|
||||
parser, projectionType) : interceptor;
|
||||
return typeCache.get(projectionType)
|
||||
? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory, parser, projectionType) : interceptor;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.ProxyProjectionFactory#isProperty(java.beans.PropertyDescriptor)
|
||||
* @see org.springframework.data.projection.ProxyProjectionFactory#getProjectionInformation(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
public ProjectionInformation getProjectionInformation(Class<?> projectionType) {
|
||||
|
||||
Method readMethod = descriptor.getReadMethod();
|
||||
return new DefaultProjectionInformation(projectionType) {
|
||||
|
||||
if (readMethod == null) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.DefaultProjectionInformation#isInputProperty(java.beans.PropertyDescriptor)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
|
||||
return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
|
||||
Method readMethod = descriptor.getReadMethod();
|
||||
|
||||
if (readMethod == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user