From f44c23878474af3d308c002465b47f1d1fcff8f1 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 16 Sep 2016 00:43:21 +0200 Subject: [PATCH] DATACMNS-912 - Fixed generic method detection for overrides using generic methods. We now correctly check the bounds of generic methods if we deal with a type variable found as method parameter. Introduced different code paths for type and method level generics so that the latter is explicitly checking all bounds available. --- .../support/DefaultRepositoryInformation.java | 53 ++++++++++++------- ...DefaultRepositoryInformationUnitTests.java | 28 +++++++++- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java index 7365d9f24..b2448ec50 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java @@ -20,6 +20,7 @@ import static org.springframework.data.repository.util.ClassUtils.*; import static org.springframework.util.ReflectionUtils.*; import java.io.Serializable; +import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -30,6 +31,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.repository.Repository; @@ -355,7 +357,7 @@ class DefaultRepositoryInformation implements RepositoryInformation { if (genericType instanceof TypeVariable) { - if (!matchesGenericType((TypeVariable) genericType, parameterType)) { + if (!matchesGenericType((TypeVariable) genericType, ResolvableType.forMethodParameter(parameter))) { return false; } @@ -375,30 +377,43 @@ class DefaultRepositoryInformation implements RepositoryInformation { * declared, the method ensures that given method parameter is the primary key type declared in the given repository * interface e.g. * - * @param name - * @param parameterType + * @param variable must not be {@literal null}. + * @param parameterType must not be {@literal null}. * @return */ - private boolean matchesGenericType(TypeVariable variable, Class parameterType) { + private boolean matchesGenericType(TypeVariable variable, ResolvableType parameterType) { - Class entityType = getDomainType(); - Class idClass = getIdType(); + GenericDeclaration declaration = variable.getGenericDeclaration(); - if (ID_TYPE_NAME.equals(variable.getName()) && parameterType.isAssignableFrom(idClass)) { - return true; + if (declaration instanceof Class) { + + ResolvableType entityType = ResolvableType.forClass(getDomainType()); + ResolvableType idClass = ResolvableType.forClass(getIdType()); + + if (ID_TYPE_NAME.equals(variable.getName()) && parameterType.isAssignableFrom(idClass)) { + return true; + } + + Type boundType = variable.getBounds()[0]; + String referenceName = boundType instanceof TypeVariable ? boundType.toString() : variable.toString(); + + boolean isDomainTypeVariableReference = DOMAIN_TYPE_NAME.equals(referenceName); + boolean parameterMatchesEntityType = parameterType.isAssignableFrom(entityType); + + // We need this check to be sure not to match save(Iterable) for entities implementing Iterable + boolean isNotIterable = !parameterType.equals(Iterable.class); + + if (isDomainTypeVariableReference && parameterMatchesEntityType && isNotIterable) { + return true; + } + + return false; } - Type boundType = variable.getBounds()[0]; - String referenceName = boundType instanceof TypeVariable ? boundType.toString() : variable.toString(); - - boolean isDomainTypeVariableReference = DOMAIN_TYPE_NAME.equals(referenceName); - boolean parameterMatchesEntityType = parameterType.isAssignableFrom(entityType); - - // We need this check to be sure not to match save(Iterable) for entities implementing Iterable - boolean isNotIterable = !parameterType.equals(Iterable.class); - - if (isDomainTypeVariableReference && parameterMatchesEntityType && isNotIterable) { - return true; + for (Type type : variable.getBounds()) { + if (ResolvableType.forType(type).isAssignableFrom(parameterType)) { + return true; + } } return false; diff --git a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java index 2d017e46f..875d65475 100644 --- a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java @@ -239,6 +239,21 @@ public class DefaultRepositoryInformationUnitTests { assertThat(information.getTargetClassMethod(source), is(expected)); } + /** + * @see DATACMNS-912 + */ + @Test + public void discoversCustomlyImplementedCrudMethodWithGenericParameters() throws Exception { + + SampleRepositoryImpl customImplementation = new SampleRepositoryImpl(); + RepositoryMetadata metadata = new DefaultRepositoryMetadata(SampleRepository.class); + RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class, + customImplementation.getClass()); + + Method customBaseRepositoryMethod = SampleRepository.class.getMethod("save", Object.class); + assertThat(information.isCustomMethod(customBaseRepositoryMethod), is(true)); + } + private static Method getMethodFrom(Class type, String name) { for (Method method : type.getMethods()) { @@ -288,7 +303,7 @@ public class DefaultRepositoryInformationUnitTests { @Override public Iterator iterator() { - return Collections. emptySet().iterator(); + return Collections.emptySet().iterator(); } } @@ -340,4 +355,15 @@ public class DefaultRepositoryInformationUnitTests { @MyQuery List findAll(); } + + interface SampleRepository extends CrudRepository {} + + static class SampleRepositoryImpl { + + public S save(S entity) { + return entity; + } + } + + static class Sample {} }