Skip to content

Commit f5972e7

Browse files
committed
Add support for ConfigurationSource and Dynamic Projections.
See #3279
1 parent 0de9b3d commit f5972e7

14 files changed

+153
-40
lines changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,22 @@ public String getLimitParameterName() {
293293
return getParameterName(queryMethod.getParameters().getLimitIndex());
294294
}
295295

296+
/**
297+
* @return the parameter name for the {@link org.springframework.data.domain.ScrollPosition scroll position parameter}
298+
* or {@code null} if the method does not declare a scroll position parameter.
299+
*/
300+
@Nullable
301+
public String getScrollPositionParameterName() {
302+
return getParameterName(queryMethod.getParameters().getScrollPositionIndex());
303+
}
304+
305+
/**
306+
* @return the parameter name for the {@link Class dynamic projection parameter} or {@code null} if the method does
307+
* not declare a dynamic projection parameter.
308+
*/
309+
@Nullable
310+
public String getDynamicProjectionParameterName() {
311+
return getParameterName(queryMethod.getParameters().getDynamicProjectionIndex());
312+
}
313+
296314
}

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

+15-24
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,25 @@ class MethodMetadata {
4848
MethodMetadata(RepositoryInformation repositoryInformation, Method method) {
4949

5050
this.returnType = repositoryInformation.getReturnType(method).toResolvableType();
51-
this.actualReturnType = ResolvableType.forType(repositoryInformation.getReturnedDomainClass(method));
51+
this.actualReturnType = repositoryInformation.getReturnedDomainTypeInformation(method).toResolvableType();
5252
this.initParameters(repositoryInformation, method, new DefaultParameterNameDiscoverer());
5353
}
5454

55-
@Nullable
56-
public String getParameterNameOf(Class<?> type) {
57-
for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
58-
if (entry.getValue().type.equals(TypeName.get(type))) {
59-
return entry.getKey();
60-
}
55+
private void initParameters(RepositoryInformation repositoryInformation, Method method,
56+
ParameterNameDiscoverer nameDiscoverer) {
57+
58+
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
59+
60+
for (java.lang.reflect.Parameter parameter : method.getParameters()) {
61+
62+
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
63+
methodParameter.initParameterNameDiscovery(nameDiscoverer);
64+
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
65+
66+
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
67+
68+
addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
6169
}
62-
return null;
6370
}
6471

6572
ResolvableType getReturnType() {
@@ -96,20 +103,4 @@ Map<String, String> getLocalVariables() {
96103
return localVariables;
97104
}
98105

99-
private void initParameters(RepositoryInformation repositoryInformation, Method method,
100-
ParameterNameDiscoverer nameDiscoverer) {
101-
102-
ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
103-
104-
for (java.lang.reflect.Parameter parameter : method.getParameters()) {
105-
106-
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
107-
methodParameter.initParameterNameDiscovery(nameDiscoverer);
108-
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
109-
110-
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
111-
112-
addParameter(ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build());
113-
}
114-
}
115106
}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,17 @@ public interface AotRepositoryContext extends AotContext {
4545
*/
4646
String getModuleName();
4747

48+
/**
49+
* @return the repository configuration source.
50+
*/
51+
RepositoryConfigurationSource getConfigurationSource();
52+
4853
/**
4954
* @return a {@link Set} of {@link String base packages} to search for repositories.
5055
*/
51-
Set<String> getBasePackages();
56+
default Set<String> getBasePackages() {
57+
return getConfigurationSource().getBasePackages().toSet();
58+
}
5259

5360
/**
5461
* @return the {@link Annotation} types used to identify domain types.

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
4646

4747
private final RegisteredBean bean;
4848
private final String moduleName;
49+
private final RepositoryConfigurationSource configurationSource;
4950
private final AotContext aotContext;
5051
private final RepositoryInformation repositoryInformation;
5152
private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
@@ -56,12 +57,14 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
5657
private String beanName;
5758

5859
public DefaultAotRepositoryContext(RegisteredBean bean, RepositoryInformation repositoryInformation,
59-
String moduleName, AotContext aotContext) {
60+
String moduleName, AotContext aotContext, RepositoryConfigurationSource configurationSource) {
6061
this.bean = bean;
6162
this.repositoryInformation = repositoryInformation;
6263
this.moduleName = moduleName;
64+
this.configurationSource = configurationSource;
6365
this.aotContext = aotContext;
6466
this.beanName = bean.getBeanName();
67+
this.basePackages = configurationSource.getBasePackages().toSet();
6568
}
6669

6770
public AotContext getAotContext() {
@@ -73,6 +76,11 @@ public String getModuleName() {
7376
return moduleName;
7477
}
7578

79+
@Override
80+
public RepositoryConfigurationSource getConfigurationSource() {
81+
return configurationSource;
82+
}
83+
7684
@Override
7785
public ConfigurableListableBeanFactory getBeanFactory() {
7886
return getAotContext().getBeanFactory();

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ private void logTrace(String message, Object... arguments) {
194194
}
195195
RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
196196
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation,
197-
extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment));
197+
extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment),
198+
configuration.getConfigurationSource());
198199

199-
repositoryContext.setBasePackages(repositoryConfiguration.getBasePackages().toSet());
200200
repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations());
201201

202202
return repositoryContext;

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

+5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ public Class<?> getReturnedDomainClass(Method method) {
8989
return getMetadata().getReturnedDomainClass(method);
9090
}
9191

92+
@Override
93+
public TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
94+
return getMetadata().getReturnedDomainTypeInformation(method);
95+
}
96+
9297
@Override
9398
public CrudMethods getCrudMethods() {
9499
return getMetadata().getCrudMethods();

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

+16
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,26 @@ default Class<?> getDomainType() {
9191
*
9292
* @param method
9393
* @return
94+
* @see #getReturnedDomainTypeInformation(Method)
9495
* @see #getReturnType(Method)
9596
*/
9697
Class<?> getReturnedDomainClass(Method method);
9798

99+
/**
100+
* Returns the domain type information returned by the given {@link Method}. In contrast to
101+
* {@link #getReturnType(Method)}, this method extracts the type from {@link Collection}s and
102+
* {@link org.springframework.data.domain.Page} as well.
103+
*
104+
* @param method
105+
* @return
106+
* @see #getReturnedDomainClass(Method)
107+
* @see #getReturnType(Method)
108+
* @since 4.0
109+
*/
110+
default TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
111+
return TypeInformation.of(getReturnedDomainClass(method));
112+
}
113+
98114
/**
99115
* Returns {@link CrudMethods} meta information for the repository.
100116
*

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,16 @@ public TypeInformation<?> getReturnType(Method method) {
100100

101101
@Override
102102
public Class<?> getReturnedDomainClass(Method method) {
103+
return getReturnedDomainTypeInformation(method).getType();
104+
}
105+
106+
@Override
107+
public TypeInformation<?> getReturnedDomainTypeInformation(Method method) {
103108

104109
TypeInformation<?> returnType = getReturnType(method);
105110
returnType = ReactiveWrapperConverters.unwrapWrapperTypes(returnType);
106111

107-
return QueryExecutionConverters.unwrapWrapperTypes(returnType, getDomainTypeInformation()).getType();
112+
return QueryExecutionConverters.unwrapWrapperTypes(returnType, getDomainTypeInformation());
108113
}
109114

110115
@Override

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ private static void assertReturnTypeAssignable(Method method, Set<Class<?>> type
403403
}
404404
}
405405

406-
throw new IllegalStateException("Method has to have one of the following return types " + types);
406+
throw new IllegalStateException(
407+
"Method '%s' has to have one of the following return types: %s".formatted(method, types));
407408
}
408409

409410
static class QueryMethodValidator {

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

+42-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,16 @@
2323
import org.junit.jupiter.api.Test;
2424
import org.mockito.Mockito;
2525

26+
import org.springframework.data.domain.Limit;
27+
import org.springframework.data.domain.Page;
2628
import org.springframework.data.domain.Pageable;
29+
import org.springframework.data.domain.ScrollPosition;
30+
import org.springframework.data.domain.Window;
31+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
32+
import org.springframework.data.repository.Repository;
2733
import org.springframework.data.repository.core.RepositoryInformation;
34+
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
35+
import org.springframework.data.repository.query.DefaultParameters;
2836
import org.springframework.data.repository.query.QueryMethod;
2937
import org.springframework.data.util.TypeInformation;
3038

@@ -45,6 +53,28 @@ void suggestLocalVariableNameConsidersMethodArguments() throws NoSuchMethodExcep
4553
assertThat(ctx.localVariable("arg0")).isNotIn("arg0", "arg1", "arg2");
4654
}
4755

56+
@Test // GH-3270
57+
void returnsCorrectParameterNames() throws NoSuchMethodException {
58+
59+
AotQueryMethodGenerationContext ctx = ctxFor("limitScrollPositionDynamicProjection");
60+
61+
assertThat(ctx.getLimitParameterName()).isEqualTo("l");
62+
assertThat(ctx.getPageableParameterName()).isNull();
63+
assertThat(ctx.getScrollPositionParameterName()).isEqualTo("sp");
64+
assertThat(ctx.getDynamicProjectionParameterName()).isEqualTo("projection");
65+
}
66+
67+
@Test // GH-3270
68+
void returnsCorrectParameterNameForPageable() throws NoSuchMethodException {
69+
70+
AotQueryMethodGenerationContext ctx = ctxFor("pageable");
71+
72+
assertThat(ctx.getLimitParameterName()).isNull();
73+
assertThat(ctx.getPageableParameterName()).isEqualTo("p");
74+
assertThat(ctx.getScrollPositionParameterName()).isNull();
75+
assertThat(ctx.getDynamicProjectionParameterName()).isNull();
76+
}
77+
4878
AotQueryMethodGenerationContext ctxFor(String methodName) throws NoSuchMethodException {
4979

5080
Method target = null;
@@ -60,13 +90,21 @@ AotQueryMethodGenerationContext ctxFor(String methodName) throws NoSuchMethodExc
6090
}
6191

6292
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
63-
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
93+
Mockito.doReturn(TypeInformation.of(String.class)).when(ri).getReturnType(eq(target));
94+
Mockito.doReturn(TypeInformation.of(String.class)).when(ri).getReturnedDomainTypeInformation(eq(target));
6495

65-
return new AotQueryMethodGenerationContext(ri, target, Mockito.mock(QueryMethod.class),
96+
return new AotQueryMethodGenerationContext(ri, target,
97+
new QueryMethod(target, AbstractRepositoryMetadata.getMetadata(DummyRepo.class),
98+
new SpelAwareProxyProjectionFactory(), DefaultParameters::new),
6699
Mockito.mock(AotRepositoryFragmentMetadata.class));
67100
}
68101

69-
private interface DummyRepo {
70-
String reservedParameterMethod(Object arg0, Pageable arg1, Object arg2);
102+
private interface DummyRepo extends Repository<String, Long> {
103+
104+
Page<String> reservedParameterMethod(Object arg0, Pageable arg1, Object arg2);
105+
106+
<T> Window<T> limitScrollPositionDynamicProjection(Limit l, ScrollPosition sp, Class<T> projection);
107+
108+
Page<String> pageable(Pageable p);
71109
}
72110
}

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
import static org.mockito.ArgumentMatchers.any;
20-
import static org.mockito.Mockito.doReturn;
21-
import static org.mockito.Mockito.when;
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
20+
import static org.mockito.Mockito.*;
2221

2322
import example.UserRepository;
2423
import example.UserRepository.User;
@@ -29,6 +28,7 @@
2928
import org.junit.jupiter.api.BeforeEach;
3029
import org.junit.jupiter.api.Test;
3130
import org.mockito.Mockito;
31+
3232
import org.springframework.core.ResolvableType;
3333
import org.springframework.data.repository.core.RepositoryInformation;
3434
import org.springframework.data.util.TypeInformation;
@@ -58,6 +58,7 @@ void generatesMethodSkeletonBasedOnGenerationMetadata() throws NoSuchMethodExcep
5858
when(methodGenerationContext.getMethod()).thenReturn(method);
5959
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
6060
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
61+
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
6162
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
6263
methodMetadata.addParameter(ParameterSpec.builder(String.class, "firstname").build());
6364
when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata);
@@ -75,6 +76,7 @@ void generatesMethodWithGenerics() throws NoSuchMethodException {
7576
when(methodGenerationContext.getReturnType())
7677
.thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
7778
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
79+
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
7880
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
7981
methodMetadata
8082
.addParameter(ParameterSpec.builder(ParameterizedTypeName.get(List.class, String.class), "firstnames").build());

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

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.core.io.ClassPathResource;
2727
import org.springframework.core.test.tools.ClassFile;
2828
import org.springframework.data.repository.config.AotRepositoryContext;
29+
import org.springframework.data.repository.config.RepositoryConfigurationSource;
2930
import org.springframework.data.repository.core.RepositoryInformation;
3031
import org.springframework.data.repository.core.support.RepositoryComposition;
3132
import org.springframework.lang.Nullable;
@@ -48,6 +49,11 @@ public String getModuleName() {
4849
return "Commons";
4950
}
5051

52+
@Override
53+
public RepositoryConfigurationSource getConfigurationSource() {
54+
return null;
55+
}
56+
5157
@Override
5258
public ConfigurableListableBeanFactory getBeanFactory() {
5359
return null;

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
import static org.mockito.ArgumentMatchers.eq;
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
2020

2121
import java.lang.reflect.Method;
2222

2323
import org.junit.jupiter.api.Test;
2424
import org.mockito.Mockito;
25+
2526
import org.springframework.data.domain.Pageable;
2627
import org.springframework.data.repository.core.RepositoryInformation;
2728
import org.springframework.data.util.TypeInformation;
@@ -73,6 +74,7 @@ static MethodMetadata methodMetadataFor(String methodName) throws NoSuchMethodEx
7374

7475
RepositoryInformation ri = Mockito.mock(RepositoryInformation.class);
7576
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnType(eq(target));
77+
Mockito.doReturn(TypeInformation.of(target.getReturnType())).when(ri).getReturnedDomainTypeInformation(eq(target));
7678
return new MethodMetadata(ri, target);
7779
}
7880

0 commit comments

Comments
 (0)