Browse Source

Add support for ConfigurationSource and Dynamic Projections.

See: #3279
Original Pull Request: #3289
pull/3304/head
Mark Paluch 7 months ago
parent
commit
857ccceb37
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 2
      pom.xml
  2. 18
      src/main/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContext.java
  3. 39
      src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java
  4. 9
      src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
  5. 10
      src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
  6. 4
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  7. 5
      src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java
  8. 16
      src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java
  9. 7
      src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java
  10. 3
      src/main/java/org/springframework/data/repository/query/QueryMethod.java
  11. 51
      src/test/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContextUnitTests.java
  12. 10
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java
  13. 6
      src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java
  14. 6
      src/test/java/org/springframework/data/repository/aot/generate/MethodMetadataUnitTests.java
  15. 14
      src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java

2
pom.xml

@ -381,7 +381,7 @@ @@ -381,7 +381,7 @@
</profiles>
<repositories>
<repository>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>

18
src/main/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContext.java

@ -293,4 +293,22 @@ public class AotQueryMethodGenerationContext { @@ -293,4 +293,22 @@ public class AotQueryMethodGenerationContext {
return getParameterName(queryMethod.getParameters().getLimitIndex());
}
/**
* @return the parameter name for the {@link org.springframework.data.domain.ScrollPosition scroll position parameter}
* or {@code null} if the method does not declare a scroll position parameter.
*/
@Nullable
public String getScrollPositionParameterName() {
return getParameterName(queryMethod.getParameters().getScrollPositionIndex());
}
/**
* @return the parameter name for the {@link Class dynamic projection parameter} or {@code null} if the method does
* not declare a dynamic projection parameter.
*/
@Nullable
public String getDynamicProjectionParameterName() {
return getParameterName(queryMethod.getParameters().getDynamicProjectionIndex());
}
}

39
src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java

@ -48,18 +48,25 @@ class MethodMetadata { @@ -48,18 +48,25 @@ class MethodMetadata {
MethodMetadata(RepositoryInformation repositoryInformation, Method method) {
this.returnType = repositoryInformation.getReturnType(method).toResolvableType();
this.actualReturnType = ResolvableType.forType(repositoryInformation.getReturnedDomainClass(method));
this.actualReturnType = repositoryInformation.getReturnedDomainTypeInformation(method).toResolvableType();
this.initParameters(repositoryInformation, method, new DefaultParameterNameDiscoverer());
}
@Nullable
public String getParameterNameOf(Class<?> type) {
for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
if (entry.getValue().type.equals(TypeName.get(type))) {
return entry.getKey();
}
private void initParameters(RepositoryInformation repositoryInformation, Method method,
ParameterNameDiscoverer nameDiscoverer) {
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
for (java.lang.reflect.Parameter parameter : method.getParameters()) {
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
methodParameter.initParameterNameDiscovery(nameDiscoverer);
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
}
return null;
}
ResolvableType getReturnType() {
@ -96,20 +103,4 @@ class MethodMetadata { @@ -96,20 +103,4 @@ class MethodMetadata {
return localVariables;
}
private void initParameters(RepositoryInformation repositoryInformation, Method method,
ParameterNameDiscoverer nameDiscoverer) {
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
for (java.lang.reflect.Parameter parameter : method.getParameters()) {
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
methodParameter.initParameterNameDiscovery(nameDiscoverer);
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
}
}
}

9
src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java

@ -45,10 +45,17 @@ public interface AotRepositoryContext extends AotContext { @@ -45,10 +45,17 @@ public interface AotRepositoryContext extends AotContext {
*/
String getModuleName();
/**
* @return the repository configuration source.
*/
RepositoryConfigurationSource getConfigurationSource();
/**
* @return a {@link Set} of {@link String base packages} to search for repositories.
*/
Set<String> getBasePackages();
default Set<String> getBasePackages() {
return getConfigurationSource().getBasePackages().toSet();
}
/**
* @return the {@link Annotation} types used to identify domain types.

10
src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java

@ -46,6 +46,7 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -46,6 +46,7 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
private final RegisteredBean bean;
private final String moduleName;
private final RepositoryConfigurationSource configurationSource;
private final AotContext aotContext;
private final RepositoryInformation repositoryInformation;
private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
@ -56,12 +57,14 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -56,12 +57,14 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
private String beanName;
public DefaultAotRepositoryContext(RegisteredBean bean, RepositoryInformation repositoryInformation,
String moduleName, AotContext aotContext) {
String moduleName, AotContext aotContext, RepositoryConfigurationSource configurationSource) {
this.bean = bean;
this.repositoryInformation = repositoryInformation;
this.moduleName = moduleName;
this.configurationSource = configurationSource;
this.aotContext = aotContext;
this.beanName = bean.getBeanName();
this.basePackages = configurationSource.getBasePackages().toSet();
}
public AotContext getAotContext() {
@ -73,6 +76,11 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -73,6 +76,11 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
return moduleName;
}
@Override
public RepositoryConfigurationSource getConfigurationSource() {
return configurationSource;
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return getAotContext().getBeanFactory();

4
src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java

@ -194,9 +194,9 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -194,9 +194,9 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
}
RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation,
extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment));
extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment),
configuration.getConfigurationSource());
repositoryContext.setBasePackages(repositoryConfiguration.getBasePackages().toSet());
repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations());
return repositoryContext;

5
src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java

@ -89,6 +89,11 @@ public abstract class RepositoryInformationSupport implements RepositoryInformat @@ -89,6 +89,11 @@ public abstract class RepositoryInformationSupport implements RepositoryInformat
return getMetadata().getReturnedDomainClass(method);
}
@Override
public TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
return getMetadata().getReturnedDomainTypeInformation(method);
}
@Override
public CrudMethods getCrudMethods() {
return getMetadata().getCrudMethods();

16
src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java

@ -91,10 +91,26 @@ public interface RepositoryMetadata { @@ -91,10 +91,26 @@ public interface RepositoryMetadata {
*
* @param method
* @return
* @see #getReturnedDomainTypeInformation(Method)
* @see #getReturnType(Method)
*/
Class<?> getReturnedDomainClass(Method method);
/**
* Returns the domain type information returned by the given {@link Method}. In contrast to
* {@link #getReturnType(Method)}, this method extracts the type from {@link Collection}s and
* {@link org.springframework.data.domain.Page} as well.
*
* @param method
* @return
* @see #getReturnedDomainClass(Method)
* @see #getReturnType(Method)
* @since 4.0
*/
default TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
return TypeInformation.of(getReturnedDomainClass(method));
}
/**
* Returns {@link CrudMethods} meta information for the repository.
*

7
src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java

@ -100,11 +100,16 @@ public abstract class AbstractRepositoryMetadata implements RepositoryMetadata { @@ -100,11 +100,16 @@ public abstract class AbstractRepositoryMetadata implements RepositoryMetadata {
@Override
public Class<?> getReturnedDomainClass(Method method) {
return getReturnedDomainTypeInformation(method).getType();
}
@Override
public TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
TypeInformation<?> returnType = getReturnType(method);
returnType = ReactiveWrapperConverters.unwrapWrapperTypes(returnType);
return QueryExecutionConverters.unwrapWrapperTypes(returnType, getDomainTypeInformation()).getType();
return QueryExecutionConverters.unwrapWrapperTypes(returnType, getDomainTypeInformation());
}
@Override

3
src/main/java/org/springframework/data/repository/query/QueryMethod.java

@ -403,7 +403,8 @@ public class QueryMethod { @@ -403,7 +403,8 @@ public class QueryMethod {
}
}
throw new IllegalStateException("Method has to have one of the following return types " + types);
throw new IllegalStateException(
"Method '%s' has to have one of the following return types: %s".formatted(method, types));
}
static class QueryMethodValidator {

51
src/test/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContextUnitTests.java

@ -15,16 +15,23 @@ @@ -15,16 +15,23 @@
*/
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Window;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.TypeInformation;
@ -45,6 +52,28 @@ class AotQueryMethodGenerationContextUnitTests { @@ -45,6 +52,28 @@ class AotQueryMethodGenerationContextUnitTests {
assertThat(ctx.localVariable("arg0")).isNotIn("arg0", "arg1", "arg2");
}
@Test // GH-3279
void returnsCorrectParameterNames() throws NoSuchMethodException {
AotQueryMethodGenerationContext ctx = ctxFor("limitScrollPositionDynamicProjection");
assertThat(ctx.getLimitParameterName()).isEqualTo("l");
assertThat(ctx.getPageableParameterName()).isNull();
assertThat(ctx.getScrollPositionParameterName()).isEqualTo("sp");
assertThat(ctx.getDynamicProjectionParameterName()).isEqualTo("projection");
}
@Test // GH-3279
void returnsCorrectParameterNameForPageable() throws NoSuchMethodException {
AotQueryMethodGenerationContext ctx = ctxFor("pageable");
assertThat(ctx.getLimitParameterName()).isNull();
assertThat(ctx.getPageableParameterName()).isEqualTo("p");
assertThat(ctx.getScrollPositionParameterName()).isNull();
assertThat(ctx.getDynamicProjectionParameterName()).isNull();
}
AotQueryMethodGenerationContext ctxFor(String methodName) throws NoSuchMethodException {
Method target = null;
@ -60,13 +89,21 @@ class AotQueryMethodGenerationContextUnitTests { @@ -60,13 +89,21 @@ class AotQueryMethodGenerationContextUnitTests {
}
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
Mockito.doReturn(TypeInformation.of(String.class)).when(ri).getReturnType(eq(target));
Mockito.doReturn(TypeInformation.of(String.class)).when(ri).getReturnedDomainTypeInformation(eq(target));
return new AotQueryMethodGenerationContext(ri, target, Mockito.mock(QueryMethod.class),
return new AotQueryMethodGenerationContext(ri, target,
new QueryMethod(target, AbstractRepositoryMetadata.getMetadata(DummyRepo.class),
new SpelAwareProxyProjectionFactory(), DefaultParameters::new),
Mockito.mock(AotRepositoryFragmentMetadata.class));
}
private interface DummyRepo {
String reservedParameterMethod(Object arg0, Pageable arg1, Object arg2);
private interface DummyRepo extends Repository<String, Long> {
Page<String> reservedParameterMethod(Object arg0, Pageable arg1, Object arg2);
<T> Window<T> limitScrollPositionDynamicProjection(Limit l, ScrollPosition sp, Class<T> projection);
Page<String> pageable(Pageable p);
}
}

10
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java

@ -15,10 +15,9 @@ @@ -15,10 +15,9 @@
*/
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import example.UserRepository;
import example.UserRepository.User;
@ -29,6 +28,7 @@ import java.util.List; @@ -29,6 +28,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.TypeInformation;
@ -58,6 +58,7 @@ class AotRepositoryMethodBuilderUnitTests { @@ -58,6 +58,7 @@ class AotRepositoryMethodBuilderUnitTests {
when(methodGenerationContext.getMethod()).thenReturn(method);
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
methodMetadata.addParameter(ParameterSpec.builder(String.class, "firstname").build());
when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata);
@ -75,6 +76,7 @@ class AotRepositoryMethodBuilderUnitTests { @@ -75,6 +76,7 @@ class AotRepositoryMethodBuilderUnitTests {
when(methodGenerationContext.getReturnType())
.thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
methodMetadata
.addParameter(ParameterSpec.builder(ParameterizedTypeName.get(List.class, String.class), "firstnames").build());

6
src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

@ -26,6 +26,7 @@ import org.springframework.core.env.Environment; @@ -26,6 +26,7 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.test.tools.ClassFile;
import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.lang.Nullable;
@ -48,6 +49,11 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { @@ -48,6 +49,11 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
return "Commons";
}
@Override
public RepositoryConfigurationSource getConfigurationSource() {
return null;
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return null;

6
src/test/java/org/springframework/data/repository/aot/generate/MethodMetadataUnitTests.java

@ -15,13 +15,14 @@ @@ -15,13 +15,14 @@
*/
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.TypeInformation;
@ -73,6 +74,7 @@ class MethodMetadataUnitTests { @@ -73,6 +74,7 @@ class MethodMetadataUnitTests {
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnedDomainTypeInformation(eq(target));
return new MethodMetadata(ri, target);
}

14
src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java

@ -27,6 +27,8 @@ import java.util.stream.Stream; @@ -27,6 +27,8 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.User;
@ -60,6 +62,16 @@ class AbstractRepositoryMetadataUnitTests { @@ -60,6 +62,16 @@ class AbstractRepositoryMetadataUnitTests {
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
}
@Test // GH-3279
void detectsProjectionTypeCorrectly() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ExtendingRepository.class);
Method method = ExtendingRepository.class.getMethod("findByFirstname", Pageable.class, String.class, Class.class);
ResolvableType resolvableType = metadata.getReturnedDomainTypeInformation(method).toResolvableType();
assertThat(resolvableType.getType()).hasToString("T");
}
@Test // DATACMNS-98
void determinesReturnTypeFromPageable() throws Exception {
@ -153,6 +165,8 @@ class AbstractRepositoryMetadataUnitTests { @@ -153,6 +165,8 @@ class AbstractRepositoryMetadataUnitTests {
Page<User> findByFirstname(Pageable pageable, String firstname);
<T> Page<T> findByFirstname(Pageable pageable, String firstname, Class<T> projectionType);
GenericType<User> someMethod();
List<Map<String, Object>> anotherMethod();

Loading…
Cancel
Save