Use full type information for identifier and domain types exposed byRepositoryMetadata.

See #2518.

$ Conflicts:
$	src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java
This commit is contained in:
Alex Nistico
2021-12-31 05:43:17 +01:00
committed by Oliver Drotbohm
parent 0ae04a219e
commit d19cd4eaeb
17 changed files with 154 additions and 82 deletions

View File

@@ -27,29 +27,48 @@ import org.springframework.data.util.TypeInformation;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
* @author Alessandro Nistico
*/ */
public interface RepositoryMetadata { public interface RepositoryMetadata {
/** /**
* Returns the id class the given class is declared for. * Returns the id {@link TypeInformation} the given class is declared for.
* *
* @return the id class of the entity managed by the repository. * @return the {@link TypeInformation} class of the entity managed by the repository.
*/ */
Class<?> getIdType(); TypeInformation<?> getIdTypeInformation();
/** /**
* Returns the domain class the repository is declared for. * Returns the domain {@link TypeInformation} the repository is declared for.
* *
* @return the domain class the repository is handling. * @return the domain class the repository is handling.
*/ */
Class<?> getDomainType(); TypeInformation<?> getDomainTypeInformation();
/** /**
* Returns the repository interface. * Returns the repository interface.
* *
* @return * @return
*/ */
Class<?> getRepositoryInterface(); Class<?> getRepositoryInterface();
/**
* Returns the raw id class the given class is declared for.
*
* @return the raw id class of the entity managed by the repository.
*/
default Class<?> getIdType() {
return getIdTypeInformation().getType();
}
/**
* Returns the raw domain class the repository is declared for.
*
* @return the raw domain class the repository is handling.
*/
default Class<?> getDomainType() {
return getDomainTypeInformation().getType();
}
/** /**
* Returns the type {@link Method} return type as it is declared in the repository. Considers suspended methods and * Returns the type {@link Method} return type as it is declared in the repository. Considers suspended methods and

View File

@@ -18,6 +18,8 @@ package org.springframework.data.repository.core.support;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.RepositoryDefinition; import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -27,14 +29,15 @@ import org.springframework.util.Assert;
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Xeno Amess * @author Xeno Amess
* @author Alessandro Nistico
*/ */
public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata { public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
private static final String NO_ANNOTATION_FOUND = String.format("Interface %%s must be annotated with @%s!", private static final String NO_ANNOTATION_FOUND = String.format("Interface %%s must be annotated with @%s!",
RepositoryDefinition.class.getName()); RepositoryDefinition.class.getName());
private final Class<?> idType; private final TypeInformation<?> idType;
private final Class<?> domainType; private final TypeInformation<?> domainType;
/** /**
* Creates a new {@link AnnotationRepositoryMetadata} instance looking up repository types from a * Creates a new {@link AnnotationRepositoryMetadata} instance looking up repository types from a
@@ -58,7 +61,7 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
* @see org.springframework.data.repository.core.RepositoryMetadata#getIdType() * @see org.springframework.data.repository.core.RepositoryMetadata#getIdType()
*/ */
@Override @Override
public Class<?> getIdType() { public TypeInformation<?> getIdTypeInformation() {
return this.idType; return this.idType;
} }
@@ -67,11 +70,11 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
* @see org.springframework.data.repository.core.RepositoryMetadata#getDomainType() * @see org.springframework.data.repository.core.RepositoryMetadata#getDomainType()
*/ */
@Override @Override
public Class<?> getDomainType() { public TypeInformation<?> getDomainTypeInformation() {
return this.domainType; return this.domainType;
} }
private Class<?> resolveIdType(Class<?> repositoryInterface) { private TypeInformation<?> resolveIdType(Class<?> repositoryInterface) {
RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class); RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class);
@@ -79,10 +82,10 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
throw new IllegalArgumentException(String.format("Could not resolve id type of %s!", repositoryInterface)); throw new IllegalArgumentException(String.format("Could not resolve id type of %s!", repositoryInterface));
} }
return annotation.idClass(); return ClassTypeInformation.from(annotation.idClass());
} }
private Class<?> resolveDomainType(Class<?> repositoryInterface) { private TypeInformation<?> resolveDomainType(Class<?> repositoryInterface) {
RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class); RepositoryDefinition annotation = AnnotationUtils.findAnnotation(repositoryInterface, RepositoryDefinition.class);
@@ -90,6 +93,6 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
throw new IllegalArgumentException(String.format("Could not resolve domain type of %s!", repositoryInterface)); throw new IllegalArgumentException(String.format("Could not resolve domain type of %s!", repositoryInterface));
} }
return annotation.domainClass(); return ClassTypeInformation.from(annotation.domainClass());
} }
} }

View File

@@ -43,6 +43,7 @@ import org.springframework.util.ClassUtils;
* @author Thomas Darimont * @author Thomas Darimont
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl * @author Christoph Strobl
* @author Alessandro Nistico
*/ */
class DefaultRepositoryInformation implements RepositoryInformation { class DefaultRepositoryInformation implements RepositoryInformation {
@@ -80,8 +81,8 @@ class DefaultRepositoryInformation implements RepositoryInformation {
* @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass() * @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass()
*/ */
@Override @Override
public Class<?> getDomainType() { public TypeInformation<?> getDomainTypeInformation() {
return metadata.getDomainType(); return metadata.getDomainTypeInformation();
} }
/* /*
@@ -89,8 +90,8 @@ class DefaultRepositoryInformation implements RepositoryInformation {
* @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass()
*/ */
@Override @Override
public Class<?> getIdType() { public TypeInformation<?> getIdTypeInformation() {
return metadata.getIdType(); return metadata.getIdTypeInformation();
} }
/* /*

View File

@@ -30,14 +30,15 @@ import org.springframework.util.Assert;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Alessandro Nistico
*/ */
public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata { public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata {
private static final String MUST_BE_A_REPOSITORY = String.format("Given type must be assignable to %s!", private static final String MUST_BE_A_REPOSITORY = String.format("Given type must be assignable to %s!",
Repository.class); Repository.class);
private final Class<?> idType; private final TypeInformation<?> idType;
private final Class<?> domainType; private final TypeInformation<?> domainType;
/** /**
* Creates a new {@link DefaultRepositoryMetadata} for the given repository interface. * Creates a new {@link DefaultRepositoryMetadata} for the given repository interface.
@@ -49,31 +50,32 @@ public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata {
super(repositoryInterface); super(repositoryInterface);
Assert.isTrue(Repository.class.isAssignableFrom(repositoryInterface), MUST_BE_A_REPOSITORY); Assert.isTrue(Repository.class.isAssignableFrom(repositoryInterface), MUST_BE_A_REPOSITORY);
List<TypeInformation<?>> arguments = ClassTypeInformation.from(repositoryInterface) // List<TypeInformation<?>> arguments = ClassTypeInformation.from(repositoryInterface)//
.getRequiredSuperTypeInformation(Repository.class)// .getRequiredSuperTypeInformation(Repository.class)//
.getTypeArguments(); .getTypeArguments();
this.domainType = resolveTypeParameter(arguments, 0, this.domainType = resolveTypeParameter(arguments, 0,
() -> String.format("Could not resolve domain type of %s!", repositoryInterface)); () -> String.format("Could not resolve domain type of %s!", repositoryInterface));
this.idType = resolveTypeParameter(arguments, 1, this.idType = resolveTypeParameter(arguments, 1,
() -> String.format("Could not resolve id type of %s!", repositoryInterface)); () -> String.format("Could not resolve id type of %s!", repositoryInterface));
} }
private static Class<?> resolveTypeParameter(List<TypeInformation<?>> arguments, int index, private static TypeInformation<?> resolveTypeParameter(List<TypeInformation<?>> arguments, int index,
Supplier<String> exceptionMessage) { Supplier<String> exceptionMessage) {
if (arguments.size() <= index || arguments.get(index) == null) { if (arguments.size() <= index || arguments.get(index) == null) {
throw new IllegalArgumentException(exceptionMessage.get()); throw new IllegalArgumentException(exceptionMessage.get());
} }
return arguments.get(index).getType(); return arguments.get(index).getGenericTypeInformation();
} }
public Class<?> getIdType() { public TypeInformation<?> getIdTypeInformation() {
return this.idType; return this.idType;
} }
public Class<?> getDomainType() { public TypeInformation<?> getDomainTypeInformation() {
return this.domainType; return this.domainType;
} }
} }

View File

@@ -45,6 +45,7 @@ import org.springframework.util.ObjectUtils;
* *
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke * @author Oliver Gierke
* @author Alessandro Nistico
* @since 2.0 * @since 2.0
*/ */
interface MethodLookups { interface MethodLookups {
@@ -120,8 +121,8 @@ interface MethodLookups {
Assert.notNull(repositoryMetadata, "Repository metadata must not be null!"); Assert.notNull(repositoryMetadata, "Repository metadata must not be null!");
this.entityType = ResolvableType.forClass(repositoryMetadata.getDomainType()); this.entityType = ResolvableType.forType(repositoryMetadata.getDomainTypeInformation().getGenericType());
this.idType = ResolvableType.forClass(repositoryMetadata.getIdType()); this.idType = ResolvableType.forType(repositoryMetadata.getIdTypeInformation().getGenericType());
this.repositoryInterface = repositoryMetadata.getRepositoryInterface(); this.repositoryInterface = repositoryMetadata.getRepositoryInterface();
} }

View File

@@ -21,6 +21,7 @@ import java.util.Set;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConditionalGenericConverter;
@@ -29,6 +30,7 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -42,6 +44,7 @@ import org.springframework.util.StringUtils;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Alessandro Nistico
*/ */
public class DomainClassConverter<T extends ConversionService & ConverterRegistry> public class DomainClassConverter<T extends ConversionService & ConverterRegistry>
implements ConditionalGenericConverter, ApplicationContextAware { implements ConditionalGenericConverter, ApplicationContextAware {
@@ -117,6 +120,12 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return repositories; return repositories;
}); });
} }
private static TypeDescriptor getIdTypeDescriptor(RepositoryInformation information) {
TypeInformation<?> idType = information.getIdTypeInformation();
return new TypeDescriptor(ResolvableType.forType(idType.getGenericType()), null, idType.getType().getAnnotations());
}
/** /**
* Converter to create domain types from any source that can be converted into the domain types identifier type. * Converter to create domain types from any source that can be converted into the domain types identifier type.
@@ -172,8 +181,9 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
Class<?> domainType = targetType.getType(); Class<?> domainType = targetType.getType();
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType); RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType);
RepositoryInformation information = repositories.getRequiredRepositoryInformation(domainType); RepositoryInformation information = repositories.getRequiredRepositoryInformation(domainType);
TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(information);
Object id = conversionService.convert(source, information.getIdType()); Object id = conversionService.convert(source, sourceType, idTypeDescriptor);
return id == null ? null : invoker.invokeFindById(id).orElse(null); return id == null ? null : invoker.invokeFindById(id).orElse(null);
} }
@@ -199,10 +209,10 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return repositoryInformation.map(it -> { return repositoryInformation.map(it -> {
Class<?> rawIdType = it.getIdType(); TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(it);
return sourceType.equals(TypeDescriptor.valueOf(rawIdType)) return sourceType.equals(idTypeDescriptor)
|| conversionService.canConvert(sourceType.getType(), rawIdType); || conversionService.canConvert(sourceType, idTypeDescriptor);
}).orElseThrow( }).orElseThrow(
() -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType))); () -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));
} }
@@ -254,8 +264,8 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
Class<?> domainType = sourceType.getType(); Class<?> domainType = sourceType.getType();
EntityInformation<Object, ?> entityInformation = repositories.getEntityInformationFor(domainType); EntityInformation<Object, ?> entityInformation = repositories.getEntityInformationFor(domainType);
Object id = entityInformation.getId(source);
return conversionService.convert(entityInformation.getId(source), targetType.getType()); return conversionService.convert(id, TypeDescriptor.forObject(id), targetType);
} }
/* /*
@@ -279,10 +289,10 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
return information.map(it -> { return information.map(it -> {
Class<?> rawIdType = it.getIdType(); TypeDescriptor idTypeDescriptor = getIdTypeDescriptor(it);
return targetType.equals(TypeDescriptor.valueOf(rawIdType)) return targetType.equals(idTypeDescriptor)
|| conversionService.canConvert(rawIdType, targetType.getType()); || conversionService.canConvert(idTypeDescriptor, targetType);
}).orElseThrow( }).orElseThrow(
() -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType))); () -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
@@ -30,6 +31,7 @@ import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -41,6 +43,7 @@ import org.springframework.util.StringUtils;
* Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories. * Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Alessandro Nistico
* @since 1.10 * @since 1.10
*/ */
class ReflectionRepositoryInvoker implements RepositoryInvoker { class ReflectionRepositoryInvoker implements RepositoryInvoker {
@@ -50,7 +53,7 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
private final Object repository; private final Object repository;
private final CrudMethods methods; private final CrudMethods methods;
private final Class<?> idType; private final TypeDescriptor idTypeDescriptor;
private final ConversionService conversionService; private final ConversionService conversionService;
/** /**
@@ -70,7 +73,8 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
this.repository = repository; this.repository = repository;
this.methods = metadata.getCrudMethods(); this.methods = metadata.getCrudMethods();
this.idType = metadata.getIdType(); TypeInformation<?> idType = metadata.getIdTypeInformation();
this.idTypeDescriptor = new TypeDescriptor(ResolvableType.forType(idType.getGenericType()), null, idType.getType().getAnnotations());
this.conversionService = conversionService; this.conversionService = conversionService;
} }
@@ -285,16 +289,17 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
protected Object convertId(Object id) { protected Object convertId(Object id) {
Assert.notNull(id, "Id must not be null!"); Assert.notNull(id, "Id must not be null!");
TypeDescriptor idDescriptor = TypeDescriptor.forObject(id);
if (idType.isInstance(id)) { if (idDescriptor.isAssignableTo(idTypeDescriptor)) {
return id; return id;
} }
Object result = conversionService.convert(id, idType); Object result = conversionService.convert(id, idDescriptor, idTypeDescriptor);
if (result == null) { if (result == null) {
throw new IllegalStateException( throw new IllegalStateException(
String.format("Identifier conversion of %s to %s unexpectedly returned null!", id, idType)); String.format("Identifier conversion of %s to %s unexpectedly returned null!", id, idTypeDescriptor.getType()));
} }
return result; return result;

View File

@@ -48,6 +48,7 @@ import org.springframework.util.ConcurrentLruCache;
* @author Thomas Darimont * @author Thomas Darimont
* @author Thomas Eizinger * @author Thomas Eizinger
* @author Christoph Strobl * @author Christoph Strobl
* @author Alessandro Nistico
*/ */
public class Repositories implements Iterable<Class<?>> { public class Repositories implements Iterable<Class<?>> {
@@ -102,10 +103,9 @@ public class Repositories implements Iterable<Class<?>> {
RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.get().getBean(name, RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.get().getBean(name,
RepositoryFactoryInformation.class); RepositoryFactoryInformation.class);
Class<?> domainType = ClassUtils
.getUserClass(repositoryFactoryInformation.getRepositoryInformation().getDomainType());
RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation(); RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation();
Class<?> domainType = ClassUtils.getUserClass(information.getDomainType());
Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes(); Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1); Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1);

View File

@@ -44,6 +44,7 @@ import org.springframework.util.ReflectionUtils;
* @author Christoph Strobl * @author Christoph Strobl
* @author Mark Paluch * @author Mark Paluch
* @author Jürgen Diez * @author Jürgen Diez
* @author Alessandro Nistico
*/ */
class TypeDiscoverer<S> implements TypeInformation<S> { class TypeDiscoverer<S> implements TypeInformation<S> {
@@ -299,6 +300,16 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
public Class<S> getType() { public Class<S> getType() {
return resolvedType.get(); return resolvedType.get();
} }
@Override
public Type getGenericType() {
return type;
}
@Override
public TypeInformation<?> getGenericTypeInformation() {
return createInfo(type);
}
/* /*
* (non-Javadoc) * (non-Javadoc)

View File

@@ -17,6 +17,7 @@ package org.springframework.data.util;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List; import java.util.List;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -27,6 +28,7 @@ import org.springframework.lang.Nullable;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
* @author Alessandro Nistico
*/ */
public interface TypeInformation<S> { public interface TypeInformation<S> {
@@ -148,6 +150,15 @@ public interface TypeInformation<S> {
* @return * @return
*/ */
Class<S> getType(); Class<S> getType();
/**
* Returns the type of the property with all resolvable generics applied
*
* @return
*/
default Type getGenericType() {
return getType();
}
/** /**
* Returns the user type of the property if proxied. * Returns the user type of the property if proxied.
@@ -170,6 +181,13 @@ public interface TypeInformation<S> {
*/ */
ClassTypeInformation<?> getRawTypeInformation(); ClassTypeInformation<?> getRawTypeInformation();
/**
* Returns a {@link TypeInformation} to represent the {@link TypeInformation} of the type of the current instance with all the generics parameters resolved.
*
* @return
*/
TypeInformation<?> getGenericTypeInformation();
/** /**
* Transparently returns the {@link java.util.Map} value type if the type is a {@link java.util.Map}, returns the * Transparently returns the {@link java.util.Map} value type if the type is a {@link java.util.Map}, returns the
* component type if the type {@link #isCollectionLike()} or the simple type if none of this applies. * component type if the type {@link #isCollectionLike()} or the simple type if none of this applies.

View File

@@ -28,6 +28,7 @@ import org.springframework.util.Assert;
* {@link TypeVariable} is being used in. * {@link TypeVariable} is being used in.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Alessandro Nistico
*/ */
class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> { class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
@@ -49,6 +50,11 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
this.variable = variable; this.variable = variable;
} }
@Override
public TypeInformation<?> getGenericTypeInformation() {
return createInfo(getTypeVariableMap().getOrDefault(variable, Object.class));
}
/* /*
* (non-Javadoc) * (non-Javadoc)

View File

@@ -37,20 +37,21 @@ import org.springframework.data.repository.core.RepositoryMetadata;
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Fabian Buch * @author Fabian Buch
* @author Alessandro Nistico
*/ */
class AbstractRepositoryMetadataUnitTests { class AbstractRepositoryMetadataUnitTests {
@Test // DATACMNS-98 @Test // DATACMNS-98
void discoversSimpleReturnTypeCorrectly() throws Exception { void discoversSimpleReturnTypeCorrectly() throws Exception {
RepositoryMetadata metadata = new DummyRepositoryMetadata(UserRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
Method method = UserRepository.class.getMethod("findSingle"); Method method = UserRepository.class.getMethod("findSingle");
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
} }
@Test // DATACMNS-98 @Test // DATACMNS-98
void resolvesTypeParameterReturnType() throws Exception { void resolvesTypeParameterReturnType() throws Exception {
RepositoryMetadata metadata = new DummyRepositoryMetadata(ConcreteRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
Method method = ConcreteRepository.class.getMethod("intermediateMethod"); Method method = ConcreteRepository.class.getMethod("intermediateMethod");
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
} }
@@ -58,7 +59,7 @@ class AbstractRepositoryMetadataUnitTests {
@Test // DATACMNS-98 @Test // DATACMNS-98
void determinesReturnTypeFromPageable() throws Exception { void determinesReturnTypeFromPageable() throws Exception {
RepositoryMetadata metadata = new DummyRepositoryMetadata(ExtendingRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(ExtendingRepository.class);
Method method = ExtendingRepository.class.getMethod("findByFirstname", Pageable.class, String.class); Method method = ExtendingRepository.class.getMethod("findByFirstname", Pageable.class, String.class);
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
} }
@@ -66,20 +67,20 @@ class AbstractRepositoryMetadataUnitTests {
@Test // DATACMNS-453 @Test // DATACMNS-453
void nonPageableRepository() { void nonPageableRepository() {
RepositoryMetadata metadata = new DummyRepositoryMetadata(UserRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
assertThat(metadata.isPagingRepository()).isFalse(); assertThat(metadata.isPagingRepository()).isFalse();
} }
@Test // DATACMNS-453 @Test // DATACMNS-453
void pageableRepository() { void pageableRepository() {
RepositoryMetadata metadata = new DummyRepositoryMetadata(PagedRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(PagedRepository.class);
assertThat(metadata.isPagingRepository()).isTrue(); assertThat(metadata.isPagingRepository()).isTrue();
} }
@Test // DATACMNS-98 @Test // DATACMNS-98
void determinesReturnTypeFromGenericType() throws Exception { void determinesReturnTypeFromGenericType() throws Exception {
RepositoryMetadata metadata = new DummyRepositoryMetadata(ExtendingRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(ExtendingRepository.class);
Method method = ExtendingRepository.class.getMethod("someMethod"); Method method = ExtendingRepository.class.getMethod("someMethod");
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(GenericType.class); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(GenericType.class);
} }
@@ -87,7 +88,7 @@ class AbstractRepositoryMetadataUnitTests {
@Test // DATACMNS-98 @Test // DATACMNS-98
void handlesGenericTypeInReturnedCollectionCorrectly() throws SecurityException, NoSuchMethodException { void handlesGenericTypeInReturnedCollectionCorrectly() throws SecurityException, NoSuchMethodException {
RepositoryMetadata metadata = new DummyRepositoryMetadata(ExtendingRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(ExtendingRepository.class);
Method method = ExtendingRepository.class.getMethod("anotherMethod"); Method method = ExtendingRepository.class.getMethod("anotherMethod");
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(Map.class); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(Map.class);
} }
@@ -143,23 +144,6 @@ class AbstractRepositoryMetadataUnitTests {
} }
class DummyRepositoryMetadata extends AbstractRepositoryMetadata {
DummyRepositoryMetadata(Class<?> repositoryInterface) {
super(repositoryInterface);
}
@SuppressWarnings("unchecked")
public Class<? extends Serializable> getIdType() {
return (Class<? extends Serializable>) ResolvableType//
.forClass(Repository.class, getRepositoryInterface()).getGeneric(1).resolve();
}
public Class<?> getDomainType() {
return ResolvableType.forClass(Repository.class, getRepositoryInterface()).getGeneric(0).resolve();
}
}
// DATACMNS-1299 // DATACMNS-1299
class Element {} class Element {}

View File

@@ -29,12 +29,15 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.ClassUtils; import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/** /**
* Unit tests for {@link DefaultRepositoryMetadata}. * Unit tests for {@link DefaultRepositoryMetadata}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Alessandro Nistico
*/ */
class DefaultRepositoryMetadataUnitTests { class DefaultRepositoryMetadataUnitTests {
@@ -79,7 +82,9 @@ class DefaultRepositoryMetadataUnitTests {
void detectsParameterizedEntitiesCorrectly() { void detectsParameterizedEntitiesCorrectly() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(GenericEntityRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(GenericEntityRepository.class);
assertThat(metadata.getDomainType()).isEqualTo(GenericEntity.class); TypeInformation<?> domainType = metadata.getDomainTypeInformation();
assertThat(domainType.getType()).isEqualTo(GenericEntity.class);
assertThat(domainType.getTypeArguments()).containsExactly(ClassTypeInformation.from(String.class));
} }
@Test @Test

View File

@@ -36,12 +36,12 @@ public final class DummyRepositoryInformation implements RepositoryInformation {
this.metadata = metadata; this.metadata = metadata;
} }
public Class<?> getIdType() { public TypeInformation<?> getIdTypeInformation() {
return metadata.getIdType(); return metadata.getIdTypeInformation();
} }
public Class<?> getDomainType() { public TypeInformation<?> getDomainTypeInformation() {
return metadata.getDomainType(); return metadata.getDomainTypeInformation();
} }
public Class<?> getRepositoryInterface() { public Class<?> getRepositoryInterface() {

View File

@@ -37,11 +37,13 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation; import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.util.ClassTypeInformation;
/** /**
* Integration test for {@link DomainClassConverter}. * Integration test for {@link DomainClassConverter}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Alessandro Nistico
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class DomainClassConverterIntegrationTests { class DomainClassConverterIntegrationTests {
@@ -64,8 +66,9 @@ class DomainClassConverterIntegrationTests {
beanFactory.registerBeanDefinition("postProcessor", new RootBeanDefinition(PredictingProcessor.class)); beanFactory.registerBeanDefinition("postProcessor", new RootBeanDefinition(PredictingProcessor.class));
beanFactory.registerBeanDefinition("repoFactory", new RootBeanDefinition(RepositoryFactoryBeanSupport.class)); beanFactory.registerBeanDefinition("repoFactory", new RootBeanDefinition(RepositoryFactoryBeanSupport.class));
doReturn(Person.class).when(information).getDomainType(); doReturn(ClassTypeInformation.from(Person.class)).when(information).getDomainTypeInformation();
doReturn(Serializable.class).when(information).getIdType(); doReturn(ClassTypeInformation.from(Serializable.class)).when(information).getIdTypeInformation();
doCallRealMethod().when(information).getDomainType();
doReturn(PersonRepository.class).when(factory).getObjectType(); doReturn(PersonRepository.class).when(factory).getObjectType();
doReturn(information).when(factory).getRepositoryInformation(); doReturn(information).when(factory).getRepositoryInformation();

View File

@@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Alessandro Nistico
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT) @MockitoSettings(strictness = Strictness.LENIENT)
@@ -85,7 +86,7 @@ class DomainClassConverterUnitTests {
converter.setApplicationContext(initContextWithRepo()); converter.setApplicationContext(initContextWithRepo());
when(service.canConvert(String.class, Long.class)).thenReturn(true); when(service.canConvert(STRING_TYPE, LONG_TYPE)).thenReturn(true);
assertMatches(true); assertMatches(true);
} }
@@ -116,7 +117,7 @@ class DomainClassConverterUnitTests {
ApplicationContext context = initContextWithRepo(); ApplicationContext context = initContextWithRepo();
converter.setApplicationContext(context); converter.setApplicationContext(context);
doReturn(1L).when(service).convert(any(), eq(Long.class)); doReturn(1L).when(service).convert(any(), eq(STRING_TYPE), eq(LONG_TYPE));
converter.convert("1", STRING_TYPE, USER_TYPE); converter.convert("1", STRING_TYPE, USER_TYPE);
@@ -133,7 +134,7 @@ class DomainClassConverterUnitTests {
GenericApplicationContext context = new GenericApplicationContext(parent); GenericApplicationContext context = new GenericApplicationContext(parent);
context.refresh(); context.refresh();
when(service.canConvert(String.class, Long.class)).thenReturn(true); when(service.canConvert(STRING_TYPE, LONG_TYPE)).thenReturn(true);
converter.setApplicationContext(context); converter.setApplicationContext(context);
assertThat(converter.matches(STRING_TYPE, USER_TYPE)).isTrue(); assertThat(converter.matches(STRING_TYPE, USER_TYPE)).isTrue();
@@ -169,7 +170,7 @@ class DomainClassConverterUnitTests {
converter.setApplicationContext(initContextWithRepo()); converter.setApplicationContext(initContextWithRepo());
when(service.canConvert(Long.class, String.class)).thenReturn(true); when(service.canConvert(LONG_TYPE, STRING_TYPE)).thenReturn(true);
assertThat(converter.matches(USER_TYPE, STRING_TYPE)).isTrue(); assertThat(converter.matches(USER_TYPE, STRING_TYPE)).isTrue();
} }

View File

@@ -48,6 +48,8 @@ import org.springframework.data.repository.core.support.DummyRepositoryFactoryBe
import org.springframework.data.repository.core.support.DummyRepositoryInformation; import org.springframework.data.repository.core.support.DummyRepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation; import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@@ -56,6 +58,7 @@ import org.springframework.util.ClassUtils;
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Jan Zeppenfeld * @author Jan Zeppenfeld
* @author Alessandro Nistico
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT) @MockitoSettings(strictness = Strictness.LENIENT)
@@ -290,7 +293,7 @@ class RepositoriesUnitTests {
static class CustomRepositoryMetadata extends DefaultRepositoryMetadata { static class CustomRepositoryMetadata extends DefaultRepositoryMetadata {
private final Class<?> domainType; private final TypeInformation<?> domainType;
/** /**
* @param repositoryInterface * @param repositoryInterface
@@ -302,7 +305,7 @@ class RepositoriesUnitTests {
String domainType = super.getDomainType().getName().concat("Entity"); String domainType = super.getDomainType().getName().concat("Entity");
try { try {
this.domainType = ClassUtils.forName(domainType, CustomRepositoryMetadata.class.getClassLoader()); this.domainType = ClassTypeInformation.from(ClassUtils.forName(domainType, CustomRepositoryMetadata.class.getClassLoader()));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -313,7 +316,7 @@ class RepositoriesUnitTests {
* @see org.springframework.data.repository.core.support.DefaultRepositoryMetadata#getDomainType() * @see org.springframework.data.repository.core.support.DefaultRepositoryMetadata#getDomainType()
*/ */
@Override @Override
public Class<?> getDomainType() { public TypeInformation<?> getDomainTypeInformation() {
return this.domainType; return this.domainType;
} }