DATACMNS-41 - Refactored DefaultRepositoryInformation to take custom implementation class into account as well.

This commit is contained in:
Oliver Gierke
2011-06-01 00:09:19 +01:00
parent 399bc5e6bf
commit 70da7b00f8
8 changed files with 178 additions and 92 deletions

View File

@@ -5,6 +5,11 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
@@ -15,9 +20,16 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.maven.ide.eclipse.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
</natures>
</projectDescription>

View File

@@ -33,7 +33,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
public class DefaultRepositoryInformation implements RepositoryInformation {
class DefaultRepositoryInformation implements RepositoryInformation {
@SuppressWarnings("rawtypes")
private static final TypeVariable<Class<Repository>>[] PARAMETERS = Repository.class.getTypeParameters();
@@ -44,71 +44,76 @@ public class DefaultRepositoryInformation implements RepositoryInformation {
private final RepositoryMetadata metadata;
private final Class<?> repositoryBaseClass;
private final Class<?> customImplementationClass;
/**
* Creates a new {@link DefaultRepositoryMetadata} for the given repository interface and repository base class.
*
* @param repositoryInterface
* @param metadata
* @param repositoryBaseClass
* @param customImplementationClass
*/
public DefaultRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass) {
public DefaultRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass, Class<?> customImplementationClass) {
Assert.notNull(metadata);
Assert.notNull(repositoryBaseClass);
this.metadata = metadata;
this.repositoryBaseClass = repositoryBaseClass;
this.customImplementationClass = customImplementationClass;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface()
*/
/*
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface()
*/
public Class<?> getRepositoryInterface() {
return metadata.getRepositoryInterface();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass()
*/
/*
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass()
*/
public Class<?> getDomainClass() {
return metadata.getDomainClass();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass()
*/
/*
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass()
*/
public Class<?> getIdClass() {
return metadata.getIdClass();
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.support.RepositoryMetadata#
* getRepositoryBaseClass()
*/
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryInformation#getRepositoryBaseClass()
*/
public Class<?> getRepositoryBaseClass() {
return this.repositoryBaseClass;
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.support.RepositoryMetadata#
* getBaseClassMethod(java.lang.reflect.Method)
*/
public Method getBaseClassMethod(Method method) {
Assert.notNull(method);
Method result = methodCache.get(method);
if (null != result) {
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryInformation#getTargetClassMethod(java.lang.reflect.Method)
*/
public Method getTargetClassMethod(Method method) {
if (methodCache.containsKey(method)) {
return methodCache.get(method);
}
Method result = getTargetClassMethod(method, customImplementationClass);
if (!result.equals(method)) {
methodCache.put(method, result);
return result;
}
result = getBaseClassMethodFor(method);
result = getTargetClassMethod(method, repositoryBaseClass);
methodCache.put(method, result);
return result;
}
@@ -118,28 +123,31 @@ public class DefaultRepositoryInformation implements RepositoryInformation {
* @param method
* @return
*/
private boolean isBaseClassMethod(Method method) {
private boolean isTargetClassMethod(Method method, Class<?> targetType) {
Assert.notNull(method);
if (targetType == null) {
return false;
}
if (method.getDeclaringClass().isAssignableFrom(repositoryBaseClass)) {
if (method.getDeclaringClass().isAssignableFrom(targetType)) {
return true;
}
return !method.equals(getBaseClassMethod(method));
return !method.equals(getTargetClassMethod(method, targetType));
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.support.RepositoryMetadata#
* getFinderMethods()
*/
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryInformation#getQueryMethods()
*/
public Iterable<Method> getQueryMethods() {
Set<Method> result = new HashSet<Method>();
for (Method method : getRepositoryInterface().getDeclaredMethods()) {
for (Method method : getRepositoryInterface().getMethods()) {
if (!isCustomMethod(method) && !isBaseClassMethod(method)) {
result.add(method);
}
@@ -149,34 +157,39 @@ public class DefaultRepositoryInformation implements RepositoryInformation {
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.support.RepositoryMetadata#isCustomMethod
* (java.lang.reflect.Method)
*/
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryInformation#isCustomMethod(java.lang.reflect.Method)
*/
public boolean isCustomMethod(Method method) {
Class<?> declaringClass = method.getDeclaringClass();
boolean isQueryMethod = declaringClass.equals(getRepositoryInterface());
boolean isRepositoryInterface = isGenericRepositoryInterface(declaringClass);
boolean isBaseClassMethod = isBaseClassMethod(method);
return !(isRepositoryInterface || isBaseClassMethod || isQueryMethod);
return isTargetClassMethod(method, customImplementationClass);
}
/**
* Returns the given base class' method if the given method (declared in the repository interface) was also declared
* at the repository base class. Returns the given method if the given base class does not declare the method given.
* Takes generics into account.
*
* Returns whether the given method is a method covered by the base implementation.
*
* @param method
* @return
*/
Method getBaseClassMethodFor(Method method) {
public boolean isBaseClassMethod(Method method) {
return isTargetClassMethod(method, repositoryBaseClass);
}
for (Method baseClassMethod : repositoryBaseClass.getMethods()) {
/**
* Returns the given target class' method if the given method (declared in the repository interface) was also declared
* at the target class. Returns the given method if the given base class does not declare the method given.
* Takes generics into account.
*
* @param method must not be {@literal null}
* @param baseClass
* @return
*/
Method getTargetClassMethod(Method method, Class<?> baseClass) {
if (baseClass == null) {
return method;
}
for (Method baseClassMethod : baseClass.getMethods()) {
// Wrong name
if (!method.getName().equals(baseClassMethod.getName())) {
@@ -200,11 +213,9 @@ public class DefaultRepositoryInformation implements RepositoryInformation {
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.support.RepositoryMetadata#
* hasCustomMethod()
*/
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryInformation#hasCustomMethod()
*/
public boolean hasCustomMethod() {
Class<?> repositoryInterface = getRepositoryInterface();

View File

@@ -125,7 +125,8 @@ public abstract class RepositoryFactorySupport {
Object customImplementation) {
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
RepositoryInformation information = getRepositoryInformation(metadata);
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
validate(information, customImplementation);
@@ -162,10 +163,11 @@ public abstract class RepositoryFactorySupport {
* Returns the {@link RepositoryInformation} for the given repository interface.
*
* @param repositoryInterface
* @param customImplementationClass
* @return
*/
private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata) {
return new DefaultRepositoryInformation(metadata, getRepositoryBaseClass(metadata));
private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, Class<?> customImplementationClass) {
return new DefaultRepositoryInformation(metadata, getRepositoryBaseClass(metadata), customImplementationClass);
}
@@ -321,9 +323,9 @@ public abstract class RepositoryFactorySupport {
Method method = invocation.getMethod();
if (isCustomMethodInvocation(invocation)) {
makeAccessible(method);
return executeMethodOn(customImplementation, method,
Method actualMethod = repositoryInformation.getTargetClassMethod(method);
makeAccessible(actualMethod);
return executeMethodOn(customImplementation, actualMethod,
invocation.getArguments());
}
@@ -333,7 +335,7 @@ public abstract class RepositoryFactorySupport {
// Lookup actual method as it might be redeclared in the interface
// and we have to use the repository instance nevertheless
Method actualMethod = repositoryInformation.getBaseClassMethod(method);
Method actualMethod = repositoryInformation.getTargetClassMethod(method);
return executeMethodOn(target, actualMethod,
invocation.getArguments());
}
@@ -387,6 +389,8 @@ public abstract class RepositoryFactorySupport {
if (null == customImplementation) {
return false;
}
return repositoryInformation.isCustomMethod(invocation.getMethod());
}

View File

@@ -22,7 +22,7 @@ import java.lang.reflect.Method;
*
* @author Oliver Gierke
*/
interface RepositoryInformation extends RepositoryMetadata {
public interface RepositoryInformation extends RepositoryMetadata {
/**
* Returns the base class to be used to create the proxy backing instance.
@@ -62,14 +62,14 @@ interface RepositoryInformation extends RepositoryMetadata {
/**
* Returns the base class method that is backing the given method. This can
* Returns the target class method that is backing the given method. This can
* be necessary if a repository interface redeclares a method of the core
* repository interface (e.g. for transaction behaviour customization).
* Returns the method itself if the base class does not implement the given
* Returns the method itself if the target class does not implement the given
* method.
*
* @param method
* @return
*/
Method getBaseClassMethod(Method method);
Method getTargetClassMethod(Method method);
}

View File

@@ -3,28 +3,36 @@ package org.springframework.data.repository.support;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.support.DefaultRepositoryMetadataUnitTests.DummyGenericRepositorySupport;
/**
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRepositoryInformationUnitTests {
@SuppressWarnings("rawtypes")
static final Class<DummyGenericRepositorySupport> REPOSITORY = DummyGenericRepositorySupport.class;
@Mock
FooRepositoryCustom customImplementation;
@Test
public void discoversRepositoryBaseClassMethod() throws Exception {
Method method = FooDao.class.getMethod("findOne", Integer.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooDao.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY);
Method method = FooRepository.class.getMethod("findOne", Integer.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY, null);
Method reference = information.getBaseClassMethodFor(method);
Method reference = information.getTargetClassMethod(method);
assertEquals(REPOSITORY, reference.getDeclaringClass());
assertThat(reference.getName(), is("findOne"));
}
@@ -32,15 +40,36 @@ public class DefaultRepositoryInformationUnitTests {
@Test
public void discoveresNonRepositoryBaseClassMethod() throws Exception {
Method method = FooDao.class.getMethod("findOne", Long.class);
Method method = FooRepository.class.getMethod("findOne", Long.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooDao.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class, null);
assertThat(information.getBaseClassMethodFor(method), is(method));
assertThat(information.getTargetClassMethod(method), is(method));
}
@Test
public void discoversCustomlyImplementedCrudMethod() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class, customImplementation.getClass());
Method source = FooRepositoryCustom.class.getMethod("save", User.class);
Method expected = customImplementation.getClass().getMethod("save", User.class);
assertThat(information.getTargetClassMethod(source), is(expected));
}
@Test
public void considersIntermediateMethodsAsFinderMethods() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class, null);
assertThat(information.hasCustomMethod(), is(false));
}
private static interface FooDao extends CrudRepository<User, Integer> {
interface FooRepository extends CrudRepository<User, Integer>, FooRepositoryCustom {
// Redeclared method
User findOne(Integer primaryKey);
@@ -48,6 +77,11 @@ public class DefaultRepositoryInformationUnitTests {
// Not a redeclared method
User findOne(Long primaryKey);
}
interface FooRepositoryCustom {
User save(User user);
}
@SuppressWarnings("unused")
private class User {
@@ -59,4 +93,15 @@ public class DefaultRepositoryInformationUnitTests {
return null;
}
}
interface BaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
T findBySomething(String something);
}
interface ConcreteRepository extends BaseRepository<User, Integer> {
User findBySomethingDifferent(String somethingDifferent);
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
@@ -24,7 +23,6 @@ import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.util.ClassUtils;

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.repository.support;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;

View File

@@ -44,6 +44,8 @@ public class RepositoryFactorySupportUnitTests {
@Mock
CrudRepository<Object, Serializable> backingRepo;
@Mock
ObjectRepositoryCustom customImplementation;
@Mock
MyQueryCreationListener listener;
@@ -72,6 +74,16 @@ public class RepositoryFactorySupportUnitTests {
verify(backingRepo, times(1)).save(any(Object.class));
}
@Test
public void invokesCustomMethodIfItRedeclaresACRUDOne() {
ObjectRepository repository = factory.getRepository(ObjectRepository.class, customImplementation);
repository.findOne(1);
verify(customImplementation, times(1)).findOne(1);
verify(backingRepo, times(0)).findOne(1);
}
class DummyRepositoryFactory extends RepositoryFactorySupport {
@@ -114,15 +126,19 @@ public class RepositoryFactorySupportUnitTests {
}
}
interface ObjectRepository extends Repository<Object, Serializable> {
interface ObjectRepository extends Repository<Object, Serializable>, ObjectRepositoryCustom {
Object findByClass(Class<?> clazz);
Object findByFoo();
Object save(Object entity);
}
interface ObjectRepositoryCustom {
Object findOne(Serializable id);
}
interface PlainQueryCreationListener extends
QueryCreationListener<RepositoryQuery> {