Skip to content

Support for projections on repository query methods #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.12.0.BUILD-SNAPSHOT</version>
<version>1.12.0.DATACMNS-89-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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;
Expand All @@ -42,20 +42,30 @@
* @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;

/*
* (non-Javadoc)
/**
* @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.resourceLoader = resourceLoader;
this.classLoader = resourceLoader.getClassLoader();
}

/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}

/*
Expand Down Expand Up @@ -85,8 +95,7 @@ public <T> T createProjection(Class<T> projectionType, Object source) {
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);
}

/*
Expand All @@ -110,18 +119,24 @@ public List<String> getInputProperties(Class<?> projectionType) {

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.
*
Expand All @@ -132,11 +147,12 @@ public List<String> getInputProperties(Class<?> projectionType) {
@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));
}

/**
Expand All @@ -153,18 +169,6 @@ protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor int
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,34 @@ protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor int
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) {

Method readMethod = descriptor.getReadMethod();

if (readMethod == null) {
return false;
}

return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
return AnnotationUtils.findAnnotation(readMethod, Value.class) == null;
}
};
}
}
Loading