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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ package org.springframework.data.repository.core.support;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
@@ -45,7 +48,7 @@ import org.springframework.util.Assert;
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable> implements
|
||||
InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware {
|
||||
InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware {
|
||||
|
||||
private RepositoryFactorySupport factory;
|
||||
|
||||
@@ -56,6 +59,7 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
private NamedQueries namedQueries;
|
||||
private MappingContext<?, ?> mappingContext;
|
||||
private ClassLoader classLoader;
|
||||
private BeanFactory beanFactory;
|
||||
private boolean lazyInit = false;
|
||||
private EvaluationContextProvider evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
|
||||
|
||||
@@ -151,6 +155,16 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactoryInformation#getEntityInformation()
|
||||
@@ -167,8 +181,8 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
*/
|
||||
public RepositoryInformation getRepositoryInformation() {
|
||||
|
||||
return this.factory.getRepositoryInformation(repositoryMetadata, customImplementation == null ? null
|
||||
: customImplementation.getClass());
|
||||
return this.factory.getRepositoryInformation(repositoryMetadata,
|
||||
customImplementation == null ? null : customImplementation.getClass());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -227,9 +241,10 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
this.factory = createRepositoryFactory();
|
||||
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
|
||||
this.factory.setNamedQueries(namedQueries);
|
||||
this.factory.setBeanClassLoader(classLoader);
|
||||
this.factory.setEvaluationContextProvider(evaluationContextProvider);
|
||||
this.factory.setRepositoryBaseClass(repositoryBaseClass);
|
||||
this.factory.setBeanClassLoader(classLoader);
|
||||
this.factory.setBeanFactory(beanFactory);
|
||||
|
||||
this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);
|
||||
|
||||
|
||||
@@ -29,11 +29,15 @@ import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
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.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
@@ -57,7 +61,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
|
||||
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
|
||||
|
||||
private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional",
|
||||
RepositoryFactorySupport.class.getClassLoader());
|
||||
@@ -72,6 +76,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
|
||||
private NamedQueries namedQueries = PropertiesBasedNamedQueries.EMPTY;
|
||||
private ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
|
||||
private EvaluationContextProvider evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
|
||||
|
||||
@@ -106,6 +111,15 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
|
||||
this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link EvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined queries.
|
||||
*
|
||||
@@ -307,9 +321,9 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware {
|
||||
|
||||
if (null == customImplementation && repositoryInformation.hasCustomMethod()) {
|
||||
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"You have custom methods in %s but not provided a custom implementation!",
|
||||
repositoryInformation.getRepositoryInterface()));
|
||||
throw new IllegalArgumentException(
|
||||
String.format("You have custom methods in %s but not provided a custom implementation!",
|
||||
repositoryInformation.getRepositoryInterface()));
|
||||
}
|
||||
|
||||
validate(repositoryInformation);
|
||||
@@ -412,8 +426,14 @@ 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, namedQueries);
|
||||
|
||||
RepositoryQuery query = lookupStrategy.resolveQuery(method, repositoryInformation, factory, namedQueries);
|
||||
|
||||
invokeListeners(query);
|
||||
queries.put(method, query);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -17,12 +17,15 @@ package org.springframework.data.repository.query;
|
||||
|
||||
import static java.lang.String.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -38,6 +41,7 @@ public class Parameter {
|
||||
private static final String POSITION_PARAMETER_TEMPLATE = "?%s";
|
||||
|
||||
private final MethodParameter parameter;
|
||||
private final boolean isDynamicProjectionParameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Parameter} for the given {@link MethodParameter}.
|
||||
@@ -47,7 +51,9 @@ public class Parameter {
|
||||
protected Parameter(MethodParameter parameter) {
|
||||
|
||||
Assert.notNull(parameter);
|
||||
|
||||
this.parameter = parameter;
|
||||
this.isDynamicProjectionParameter = isDynamicProjectionParameter(parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +63,7 @@ public class Parameter {
|
||||
* @see #TYPES
|
||||
*/
|
||||
public boolean isSpecialParameter() {
|
||||
return TYPES.contains(parameter.getParameterType());
|
||||
return isDynamicProjectionParameter || TYPES.contains(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +75,15 @@ public class Parameter {
|
||||
return !isSpecialParameter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link Parameter} is the one used for dynamic projections.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isDynamicProjectionParameter() {
|
||||
return isDynamicProjectionParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the placeholder to be used for the parameter. Can either be a named one or positional.
|
||||
*
|
||||
@@ -157,4 +172,31 @@ public class Parameter {
|
||||
boolean isSort() {
|
||||
return Sort.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link MethodParameter} is a dynamic projection parameter, which means it carries a
|
||||
* dynamic type parameter which is identical to the type parameter of the actually returned type.
|
||||
* <p>
|
||||
* <code>
|
||||
* <T> Collection<T> findBy…(…, Class<T> type);
|
||||
* </code>
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isDynamicProjectionParameter(MethodParameter parameter) {
|
||||
|
||||
Method method = parameter.getMethod();
|
||||
|
||||
ClassTypeInformation<?> ownerType = ClassTypeInformation.from(parameter.getDeclaringClass());
|
||||
TypeInformation<?> parameterTypes = ownerType.getParameterTypes(method).get(parameter.getParameterIndex());
|
||||
TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
|
||||
|
||||
if (!parameterTypes.getType().equals(Class.class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeInformation<?> bound = parameterTypes.getTypeArguments().get(0);
|
||||
return bound.equals(returnType.getActualType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ public interface ParameterAccessor extends Iterable<Object> {
|
||||
*/
|
||||
Sort getSort();
|
||||
|
||||
/**
|
||||
* Returns the dynamic projection type to be used when executing the query or {@literal null} if none is defined.
|
||||
*
|
||||
* @return
|
||||
* @since 1.12
|
||||
*/
|
||||
Class<?> getDynamicProjection();
|
||||
|
||||
/**
|
||||
* Returns the bindable value with the given index. Bindable means, that {@link Pageable} and {@link Sort} values are
|
||||
* skipped without noticed in the index. For a method signature taking {@link String}, {@link Pageable} ,
|
||||
@@ -66,4 +74,4 @@ public interface ParameterAccessor extends Iterable<Object> {
|
||||
* @return
|
||||
*/
|
||||
Iterator<Object> iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,13 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter> implements Iterable<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")//
|
||||
@SuppressWarnings("unchecked") //
|
||||
public static final List<Class<?>> TYPES = Arrays.asList(Pageable.class, Sort.class);
|
||||
|
||||
private static final String PARAM_ON_SPECIAL = format("You must not user @%s on a parameter typed %s or %s",
|
||||
Param.class.getSimpleName(), Pageable.class.getSimpleName(), Sort.class.getSimpleName());
|
||||
private static final String ALL_OR_NOTHING = String.format("Either use @%s "
|
||||
+ "on all parameters except %s and %s typed once, or none at all!", Param.class.getSimpleName(),
|
||||
private static final String ALL_OR_NOTHING = String.format(
|
||||
"Either use @%s on all parameters except %s and %s typed once, or none at all!", Param.class.getSimpleName(),
|
||||
Pageable.class.getSimpleName(), Sort.class.getSimpleName());
|
||||
|
||||
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
|
||||
@@ -51,6 +51,8 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
private final int sortIndex;
|
||||
private final List<T> parameters;
|
||||
|
||||
private int dynamicProjectionIndex;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Parameters}.
|
||||
*
|
||||
@@ -61,6 +63,7 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
Assert.notNull(method);
|
||||
|
||||
this.parameters = new ArrayList<T>();
|
||||
this.dynamicProjectionIndex = -1;
|
||||
|
||||
List<Class<?>> types = Arrays.asList(method.getParameterTypes());
|
||||
|
||||
@@ -75,6 +78,10 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
throw new IllegalArgumentException(PARAM_ON_SPECIAL);
|
||||
}
|
||||
|
||||
if (parameter.isDynamicProjectionParameter()) {
|
||||
this.dynamicProjectionIndex = parameter.getIndex();
|
||||
}
|
||||
|
||||
parameters.add(parameter);
|
||||
}
|
||||
|
||||
@@ -95,6 +102,7 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
|
||||
int pageableIndexTemp = -1;
|
||||
int sortIndexTemp = -1;
|
||||
int dynamicProjectionTemp = -1;
|
||||
|
||||
for (int i = 0; i < originals.size(); i++) {
|
||||
|
||||
@@ -103,10 +111,12 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
|
||||
pageableIndexTemp = original.isPageable() ? i : -1;
|
||||
sortIndexTemp = original.isSort() ? i : -1;
|
||||
dynamicProjectionTemp = original.isDynamicProjectionParameter() ? i : -1;
|
||||
}
|
||||
|
||||
this.pageableIndex = pageableIndexTemp;
|
||||
this.sortIndex = sortIndexTemp;
|
||||
this.dynamicProjectionIndex = dynamicProjectionTemp;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,6 +165,25 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
return sortIndex != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the parameter that represents the dynamic projection type. Will return {@literal -1} if no
|
||||
* such parameter exists.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getDynamicProjectionIndex() {
|
||||
return dynamicProjectionIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a parameter expressing a dynamic projection exists.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasDynamicProjection() {
|
||||
return dynamicProjectionIndex != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we potentially find a {@link Sort} parameter in the parameters.
|
||||
*
|
||||
|
||||
@@ -97,6 +97,15 @@ public class ParametersParameterAccessor implements ParameterAccessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic projection type if available, {@literal null} otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Class<?> getDynamicProjection() {
|
||||
return parameters.hasDynamicProjection() ? (Class<?>) values.get(parameters.getDynamicProjectionIndex()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value with the given index.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.repository.query;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -52,10 +53,12 @@ public interface QueryLookupStrategy {
|
||||
/**
|
||||
* Resolves a {@link RepositoryQuery} from the given {@link QueryMethod} that can be executed afterwards.
|
||||
*
|
||||
* @param method
|
||||
* @param metadata
|
||||
* @param namedQueries
|
||||
* @param method will never be {@literal null}.
|
||||
* @param metadata will never be {@literal null}.
|
||||
* @param factory will never be {@literal null}.
|
||||
* @param namedQueries will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries);
|
||||
RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
NamedQueries namedQueries);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||
@@ -44,6 +45,7 @@ public class QueryMethod {
|
||||
private final Method method;
|
||||
private final Class<?> unwrappedReturnType;
|
||||
private final Parameters<?, ?> parameters;
|
||||
private final ResultProcessor resultProcessor;
|
||||
|
||||
private Class<?> domainClass;
|
||||
|
||||
@@ -51,13 +53,15 @@ public class QueryMethod {
|
||||
* Creates a new {@link QueryMethod} from the given parameters. Looks up the correct query to use for following
|
||||
* invocations of the method given.
|
||||
*
|
||||
* @param method must not be {@literal null}
|
||||
* @param metadata must not be {@literal null}
|
||||
* @param method must not be {@literal null}.
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
*/
|
||||
public QueryMethod(Method method, RepositoryMetadata metadata) {
|
||||
public QueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(metadata, "Repository metadata must not be null!");
|
||||
Assert.notNull(factory, "ProjectionFactory must not be null!");
|
||||
|
||||
for (Class<?> type : Parameters.TYPES) {
|
||||
if (getNumberOfOccurences(method, type) > 1) {
|
||||
@@ -89,6 +93,8 @@ public class QueryMethod {
|
||||
Assert.isTrue(this.parameters.hasPageableParameter(),
|
||||
String.format("Paging query needs to have a Pageable parameter! Offending method %s", method.toString()));
|
||||
}
|
||||
|
||||
this.resultProcessor = new ResultProcessor(this, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,6 +234,15 @@ public class QueryMethod {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ResultProcessor} to be usedwith the query method.
|
||||
*
|
||||
* @return the resultFactory
|
||||
*/
|
||||
public ResultProcessor getResultProcessor() {
|
||||
return resultProcessor;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.repository.query;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ResultProcessor} to expose metadata about query result element projection and eventually post prcessing raw
|
||||
* query results into projections and data transfer objects.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
public class ResultProcessor {
|
||||
|
||||
private final QueryMethod method;
|
||||
private final ProjectingConverter converter;
|
||||
private final ProjectionFactory factory;
|
||||
|
||||
private ReturnedType type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResultProcessor} from the given {@link QueryMethod} and {@link ProjectionFactory}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
*/
|
||||
ResultProcessor(QueryMethod method, ProjectionFactory factory) {
|
||||
this(method, factory, method.getReturnedObjectType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResultProcessor} for the given {@link QueryMethod}, {@link ProjectionFactory} and type.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
private ResultProcessor(QueryMethod method, ProjectionFactory factory, Class<?> type) {
|
||||
|
||||
Assert.notNull(method, "QueryMethod must not be null!");
|
||||
Assert.notNull(factory, "ProjectionFactory must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
this.method = method;
|
||||
this.type = ReturnedType.of(type, method.getDomainClass(), factory);
|
||||
this.converter = new ProjectingConverter(this.type, factory);
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link ResultProcessor} with a new projection type obtained from the given {@link ParameterAccessor}.
|
||||
*
|
||||
* @param accessor can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ResultProcessor withDynamicProjection(ParameterAccessor accessor) {
|
||||
|
||||
if (accessor == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Class<?> projectionType = accessor.getDynamicProjection();
|
||||
|
||||
return projectionType == null ? this : new ResultProcessor(method, factory, projectionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ReturnedType}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ReturnedType getReturnedType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes the given query result.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> T processResult(Object source) {
|
||||
return processResult(source, NoOpConverter.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes the given query result using the given preparing {@link Converter} to potentially prepare collection
|
||||
* elements.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @param preparingConverter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T processResult(Object source, Converter<Object, Object> preparingConverter) {
|
||||
|
||||
if (type.isInstance(source) || !type.isProjecting()) {
|
||||
return (T) source;
|
||||
}
|
||||
|
||||
Assert.notNull(preparingConverter, "Preparing converter must not be null!");
|
||||
|
||||
ChainingConverter converter = ChainingConverter.of(preparingConverter).and(this.converter);
|
||||
|
||||
if (source instanceof Page && method.isPageQuery()) {
|
||||
return (T) ((Page<?>) source).map(converter);
|
||||
}
|
||||
|
||||
if (source instanceof Collection && method.isCollectionQuery()) {
|
||||
|
||||
Collection<?> collection = (Collection<?>) source;
|
||||
Collection<Object> target = CollectionFactory.createCollection(collection.getClass(), collection.size());
|
||||
|
||||
for (Object columns : collection) {
|
||||
target.add(type.isInstance(columns) ? columns : converter.convert(columns));
|
||||
}
|
||||
|
||||
return (T) target;
|
||||
}
|
||||
|
||||
return (T) converter.convert(source);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
private static class ChainingConverter implements Converter<Object, Object> {
|
||||
|
||||
private final @NonNull Converter<Object, Object> delegate;
|
||||
|
||||
/**
|
||||
* Returns a new {@link ChainingConverter} that hands the elements resulting from the current conversion to the
|
||||
* given {@link Converter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ChainingConverter and(final Converter<Object, Object> converter) {
|
||||
|
||||
Assert.notNull(converter, "Converter must not be null!");
|
||||
|
||||
return new ChainingConverter(new Converter<Object, Object>() {
|
||||
|
||||
@Override
|
||||
public Object convert(Object source) {
|
||||
return converter.convert(ChainingConverter.this.convert(source));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source) {
|
||||
return delegate.convert(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple {@link Converter} that will return the source value as is.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
private static enum NoOpConverter implements Converter<Object, Object> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class ProjectingConverter implements Converter<Object, Object> {
|
||||
|
||||
private final @NonNull ReturnedType type;
|
||||
private final @NonNull ProjectionFactory factory;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source) {
|
||||
return factory.createProjection(type.getReturnedType(), getProjectionTarget(source));
|
||||
}
|
||||
|
||||
private Object getProjectionTarget(Object source) {
|
||||
|
||||
if (source != null && source.getClass().isArray()) {
|
||||
source = Arrays.asList((Object[]) source);
|
||||
}
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return toMap((Collection<?>) source, type.getInputProperties());
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
private static Map<String, Object> toMap(Collection<?> values, List<String> names) {
|
||||
|
||||
int i = 0;
|
||||
Map<String, Object> result = new HashMap<String, Object>(values.size());
|
||||
|
||||
for (Object element : values) {
|
||||
result.put(names.get(i++), element);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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.repository.query;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A representation of the type returned by a {@link QueryMethod}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public abstract class ReturnedType {
|
||||
|
||||
private final @NonNull Class<?> domainType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReturnedType} for the given returned type, domain type and {@link ProjectionFactory}.
|
||||
*
|
||||
* @param returnedType must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static ReturnedType of(Class<?> returnedType, Class<?> domainType, ProjectionFactory factory) {
|
||||
|
||||
Assert.notNull(returnedType, "Returned type must not be null!");
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
Assert.notNull(factory, "ProjectionFactory must not be null!");
|
||||
|
||||
return (ReturnedType) (returnedType.isInterface()
|
||||
? new ReturnedInterface(factory.getProjectionInformation(returnedType), domainType)
|
||||
: new ReturnedClass(returnedType, domainType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity type.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public final Class<?> getDomainType() {
|
||||
return domainType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given source object is an instance of the returned type.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public final boolean isInstance(Object source) {
|
||||
return getReturnedType().isInstance(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the type is projecting, i.e. not of the domain type.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean isProjecting();
|
||||
|
||||
/**
|
||||
* Returns the type of the individual objects to return.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Class<?> getReturnedType();
|
||||
|
||||
/**
|
||||
* Returns whether the returned type will require custom construction.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean needsCustomConstruction();
|
||||
|
||||
/**
|
||||
* Returns the type that the query execution is supposed to pass to the underlying infrastructure. {@literal null} is
|
||||
* returned to indicate a generic type (a map or tuple-like type) shall be used.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Class<?> getTypeToRead();
|
||||
|
||||
/**
|
||||
* Returns the properties required to be used to populate the result.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract List<String> getInputProperties();
|
||||
|
||||
/**
|
||||
* A {@link ReturnedType} that's backed by an interface.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
private static final class ReturnedInterface extends ReturnedType {
|
||||
|
||||
private final ProjectionInformation information;
|
||||
private final Class<?> domainType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReturnedInterface} from the given {@link ProjectionInformation} and domain type.
|
||||
*
|
||||
* @param information must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
*/
|
||||
public ReturnedInterface(ProjectionInformation information, Class<?> domainType) {
|
||||
|
||||
super(domainType);
|
||||
|
||||
Assert.notNull(information, "Projection information must not be null!");
|
||||
|
||||
this.information = information;
|
||||
this.domainType = domainType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getReturnedType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getReturnedType() {
|
||||
return information.getType();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ReturnedType#needsCustomConstruction()
|
||||
*/
|
||||
public boolean needsCustomConstruction() {
|
||||
return information.isClosed();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedType#isProjecting()
|
||||
*/
|
||||
@Override
|
||||
public boolean isProjecting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getTypeToRead()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getTypeToRead() {
|
||||
return information.isClosed() ? null : domainType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getInputProperties()
|
||||
*/
|
||||
@Override
|
||||
public List<String> getInputProperties() {
|
||||
|
||||
List<String> properties = new ArrayList<String>();
|
||||
|
||||
for (PropertyDescriptor descriptor : information.getInputProperties()) {
|
||||
properties.add(descriptor.getName());
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ReturnedType} that's backed by an actual class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.12
|
||||
*/
|
||||
private static final class ReturnedClass extends ReturnedType {
|
||||
|
||||
@SuppressWarnings("unchecked") //
|
||||
private static final Set<Class<?>> VOID_TYPES = new HashSet<Class<?>>(Arrays.asList(Void.class, void.class));
|
||||
|
||||
private final Class<?> type;
|
||||
private final List<String> inputProperties;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReturnedClass} instance for the given returned type and domain type.
|
||||
*
|
||||
* @param returnedType must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param projectionInformation
|
||||
*/
|
||||
public ReturnedClass(Class<?> returnedType, Class<?> domainType) {
|
||||
|
||||
super(domainType);
|
||||
|
||||
Assert.notNull(returnedType, "Returned type must not be null!");
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
Assert.isTrue(!returnedType.isInterface(), "Returned type must not be an interface!");
|
||||
|
||||
this.type = returnedType;
|
||||
this.inputProperties = detectConstructorParameterNames(returnedType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getReturnedType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getReturnedType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedType#getTypeToRead()
|
||||
*/
|
||||
public Class<?> getTypeToRead() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedType#isProjecting()
|
||||
*/
|
||||
@Override
|
||||
public boolean isProjecting() {
|
||||
return isDto();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedType#needsCustomConstruction()
|
||||
*/
|
||||
public boolean needsCustomConstruction() {
|
||||
return isDto();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ResultFactory.ReturnedTypeInformation#getInputProperties()
|
||||
*/
|
||||
@Override
|
||||
public List<String> getInputProperties() {
|
||||
return inputProperties;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<String> detectConstructorParameterNames(Class<?> type) {
|
||||
|
||||
if (!isDto()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
PreferredConstructorDiscoverer<?, ?> discoverer = new PreferredConstructorDiscoverer(type);
|
||||
PreferredConstructor<?, ?> constructor = discoverer.getConstructor();
|
||||
List<String> properties = new ArrayList<String>();
|
||||
|
||||
for (PreferredConstructor.Parameter<Object, ?> parameter : constructor.getParameters()) {
|
||||
properties.add(parameter.getName());
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private boolean isDto() {
|
||||
return !Object.class.equals(type) && //
|
||||
!isDomainSubtype() && //
|
||||
!isPrimitiveOrWrapper() && //
|
||||
!VOID_TYPES.contains(type) && //
|
||||
!type.getPackage().getName().startsWith("java.lang");
|
||||
}
|
||||
|
||||
private boolean isDomainSubtype() {
|
||||
return getDomainType().equals(type) && getDomainType().isAssignableFrom(type);
|
||||
}
|
||||
|
||||
private boolean isPrimitiveOrWrapper() {
|
||||
return ClassUtils.isPrimitiveOrWrapper(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2011 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -21,7 +21,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface to access property types and resolving generics on the way. Starting with a {@link ClassTypeInformation}
|
||||
* you can travers properties using {@link #getProperty(String)} to access type information.
|
||||
* you can traverse properties using {@link #getProperty(String)} to access type information.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.web;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
@@ -37,7 +38,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
* @since 1.10
|
||||
*/
|
||||
public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodProcessor
|
||||
implements BeanFactoryAware, ResourceLoaderAware {
|
||||
implements BeanFactoryAware, ResourceLoaderAware, BeanClassLoaderAware {
|
||||
|
||||
private final SpelAwareProxyProjectionFactory proxyFactory;
|
||||
private final ConversionService conversionService;
|
||||
@@ -64,15 +65,25 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP
|
||||
this.proxyFactory.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
* @deprecated rather set the {@link ClassLoader} via {@link #setBeanClassLoader(ClassLoader)}.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.proxyFactory.setResourceLoader(resourceLoader);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
|
||||
*/
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.proxyFactory.setBeanClassLoader(classLoader);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
|
||||
Reference in New Issue
Block a user