Skip to content

Commit 39697c7

Browse files
committed
Introduce ParametersSource to Parameters.
We now provide a ParametersSource object to create MethodParameter objects associated with the enclosing class so parameters can resolve generics properly.
1 parent 3722208 commit 39697c7

File tree

7 files changed

+207
-34
lines changed

7 files changed

+207
-34
lines changed

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,13 @@ public DefaultParameters(Method method) {
3939
}
4040

4141
/**
42-
* Creates a new {@link DefaultParameters} instance from the given {@link Method} and aggregate
43-
* {@link TypeInformation}.
42+
* Creates a new {@link DefaultParameters} instance from the given {@link ParametersSource}.
4443
*
45-
* @param method must not be {@literal null}.
46-
* @param aggregateType must not be {@literal null}.
44+
* @param parametersSource must not be {@literal null}.
45+
* @since 3.2.1
4746
*/
48-
public DefaultParameters(Method method, TypeInformation<?> aggregateType) {
49-
super(method, param -> new Parameter(param, aggregateType));
47+
public DefaultParameters(ParametersSource parametersSource) {
48+
super(parametersSource, param -> new Parameter(param, parametersSource.getDomainType()));
5049
}
5150

5251
private DefaultParameters(List<Parameter> parameters) {

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

+24-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
*/
4545
public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter> implements Streamable<T> {
4646

47-
public static final List<Class<?>> TYPES = Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class, Limit.class);
47+
public static final List<Class<?>> TYPES = Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class,
48+
Limit.class);
4849

4950
private static final String PARAM_ON_SPECIAL = format("You must not use @%s on a parameter typed %s or %s",
5051
Param.class.getSimpleName(), Pageable.class.getSimpleName(), Sort.class.getSimpleName());
@@ -82,14 +83,30 @@ public Parameters(Method method) {
8283
* @param method must not be {@literal null}.
8384
* @param parameterFactory must not be {@literal null}.
8485
* @since 3.0.2
86+
* @deprecated since 3.2.1, use {@link Parameters(ParametersSource, Function)} instead.
8587
*/
88+
@Deprecated(since = "3.2.1", forRemoval = true)
8689
protected Parameters(Method method, Function<MethodParameter, T> parameterFactory) {
90+
this(ParametersSource.of(method), parameterFactory);
91+
}
92+
93+
/**
94+
* Creates a new {@link Parameters} instance for the given {@link Method} and {@link Function} to create a
95+
* {@link Parameter} instance from a {@link MethodParameter}.
96+
*
97+
* @param parametersSource must not be {@literal null}.
98+
* @param parameterFactory must not be {@literal null}.
99+
* @since 3.2.1
100+
*/
101+
protected Parameters(ParametersSource parametersSource,
102+
Function<MethodParameter, T> parameterFactory) {
87103

88-
Assert.notNull(method, "Method must not be null");
104+
Assert.notNull(parametersSource, "ParametersSource must not be null");
89105

90106
// Factory nullability not enforced yet to support falling back to the deprecated
91107
// createParameter(MethodParameter). Add assertion when the deprecation is removed.
92108

109+
Method method = parametersSource.getMethod();
93110
int parameterCount = method.getParameterCount();
94111

95112
this.parameters = new ArrayList<>(parameterCount);
@@ -102,7 +119,9 @@ protected Parameters(Method method, Function<MethodParameter, T> parameterFactor
102119

103120
for (int i = 0; i < parameterCount; i++) {
104121

105-
MethodParameter methodParameter = new MethodParameter(method, i);
122+
MethodParameter methodParameter = new MethodParameter(method, i)
123+
.withContainingClass(parametersSource.getContainingClass());
124+
106125
methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
107126

108127
T parameter = parameterFactory == null //
@@ -421,7 +440,9 @@ public static boolean isBindable(Class<?> type) {
421440
return !TYPES.contains(type);
422441
}
423442

443+
@Override
424444
public Iterator<T> iterator() {
425445
return parameters.iterator();
426446
}
447+
427448
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.query;
17+
18+
import java.lang.reflect.Method;
19+
20+
import org.springframework.data.repository.core.RepositoryMetadata;
21+
import org.springframework.data.util.TypeInformation;
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Interface providing access to the method, containing class and domain type as source object for parameter
26+
* descriptors.
27+
*
28+
* @author Mark Paluch
29+
* @since 3.2.1
30+
*/
31+
public interface ParametersSource {
32+
33+
/**
34+
* Create a new parameter source for the given {@link Method}.
35+
*
36+
* @param method the method to use.
37+
* @return a new parameter source for the given {@link Method}.
38+
*/
39+
static ParametersSource of(Method method) {
40+
41+
Assert.notNull(method, "Method must not be null");
42+
43+
return new ParametersSource() {
44+
45+
@Override
46+
public Class<?> getContainingClass() {
47+
return method.getDeclaringClass();
48+
}
49+
50+
@Override
51+
public Method getMethod() {
52+
return method;
53+
}
54+
55+
@Override
56+
public TypeInformation<?> getDomainType() {
57+
return TypeInformation.OBJECT;
58+
}
59+
};
60+
}
61+
62+
/**
63+
* Create a new parameter source for the given {@link Method} in the context of {@link RepositoryMetadata}.
64+
*
65+
* @param method the method to use.
66+
* @return a new parameter source for the given {@link Method}.
67+
*/
68+
static ParametersSource of(RepositoryMetadata metadata, Method method) {
69+
70+
Assert.notNull(metadata, "RepositoryMetadata must not be null");
71+
Assert.notNull(method, "Method must not be null");
72+
73+
if (!method.getDeclaringClass().isAssignableFrom(metadata.getRepositoryInterface())) {
74+
throw new IllegalArgumentException(
75+
"Method " + method + " is not defined in the type hierarchy of " + metadata.getRepositoryInterface());
76+
}
77+
78+
return new ParametersSource() {
79+
80+
@Override
81+
public Class<?> getContainingClass() {
82+
return metadata.getRepositoryInterface();
83+
}
84+
85+
@Override
86+
public Method getMethod() {
87+
return method;
88+
}
89+
90+
@Override
91+
public TypeInformation<?> getDomainType() {
92+
return metadata.getDomainTypeInformation();
93+
}
94+
};
95+
}
96+
97+
/**
98+
* @return the class that contains the {@link #getMethod()}.
99+
*/
100+
Class<?> getContainingClass();
101+
102+
/**
103+
* @return the method for which the parameter source is defined.
104+
*/
105+
Method getMethod();
106+
107+
/**
108+
* @return the domain type associated with the parameter source.
109+
*/
110+
TypeInformation<?> getDomainType();
111+
112+
}

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -183,16 +183,29 @@ private boolean calculateIsCollectionQuery() {
183183
* @param method must not be {@literal null}.
184184
* @param domainType must not be {@literal null}.
185185
* @return must not return {@literal null}.
186+
* @deprecated since 3.2.1
186187
* @since 3.0.2
187188
*/
189+
@Deprecated(since = "3.2.1", forRemoval = true)
188190
protected Parameters<?, ?> createParameters(Method method, TypeInformation<?> domainType) {
189-
return new DefaultParameters(method, domainType);
191+
return createParameters(ParametersSource.of(getMetadata(), method));
192+
}
193+
194+
/**
195+
* Creates a {@link Parameters} instance.
196+
*
197+
* @param parametersSource must not be {@literal null}.
198+
* @return must not return {@literal null}.
199+
* @since 3.2.1
200+
*/
201+
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
202+
return new DefaultParameters(parametersSource);
190203
}
191204

192205
/**
193206
* Returns the method's name.
194207
*
195-
* @return
208+
* @return the method's name.
196209
*/
197210
public String getName() {
198211
return method.getName();

src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ void capturesImperativeDurationCorrectly() throws Exception {
114114

115115
repositoryMethodInvoker("findAll").invoke();
116116

117-
assertThat(multicaster.first().getDuration(TimeUnit.MILLISECONDS)).isCloseTo(250, Percentage.withPercentage(10));
117+
assertThat(multicaster.first()
118+
.getDuration(TimeUnit.MILLISECONDS)).isCloseTo(500, Percentage.withPercentage(50));
118119
}
119120

120121
@Test // DATACMNS-1764
@@ -124,7 +125,8 @@ void capturesReactiveDurationCorrectly() throws Exception {
124125

125126
repositoryMethodInvokerForReactive("findAll").<Flux<TestDummy>> invoke().subscribe();
126127

127-
assertThat(multicaster.first().getDuration(TimeUnit.MILLISECONDS)).isCloseTo(250, Percentage.withPercentage(10));
128+
assertThat(multicaster.first()
129+
.getDuration(TimeUnit.MILLISECONDS)).isCloseTo(500, Percentage.withPercentage(50));
128130
}
129131

130132
@Test // DATACMNS-1764

src/test/java/org/springframework/data/repository/query/ParametersParameterAccessorUnitTests.java

+15-13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.data.domain.ScrollPosition;
2626
import org.springframework.data.domain.Sort;
2727
import org.springframework.data.domain.Sort.Order;
28+
import org.springframework.data.repository.core.RepositoryMetadata;
2829

2930
/**
3031
* Unit tests for {@link ParametersParameterAccessor}.
@@ -36,10 +37,11 @@
3637
class ParametersParameterAccessorUnitTests {
3738

3839
Parameters<?, ?> parameters;
40+
RepositoryMetadata metadata;
3941

4042
@BeforeEach
4143
void setUp() throws Exception {
42-
parameters = new DefaultParameters(Sample.class.getMethod("method", String.class, int.class));
44+
parameters = new DefaultParameters(ParametersSource.of(Sample.class.getMethod("method", String.class, int.class)));
4345
}
4446

4547
@Test
@@ -62,7 +64,7 @@ void detectsNullValue() throws Exception {
6264
assertThat(accessor.hasBindableNullValue()).isTrue();
6365

6466
var method = Sample.class.getMethod("method", Pageable.class, String.class);
65-
var parameters = new DefaultParameters(method);
67+
var parameters = new DefaultParameters(ParametersSource.of(method));
6668

6769
accessor = new ParametersParameterAccessor(parameters, new Object[] { null, "Foo" });
6870
assertThat(accessor.hasBindableNullValue()).isFalse();
@@ -72,7 +74,7 @@ void detectsNullValue() throws Exception {
7274
void iteratesonlyOverBindableValues() throws Exception {
7375

7476
var method = Sample.class.getMethod("method", Pageable.class, String.class);
75-
var parameters = new DefaultParameters(method);
77+
var parameters = new DefaultParameters(ParametersSource.of(method));
7678

7779
var accessor = new ParametersParameterAccessor(parameters, new Object[] { PageRequest.of(0, 10), "Foo" });
7880

@@ -84,7 +86,7 @@ void iteratesonlyOverBindableValues() throws Exception {
8486
void handlesScrollPositionAsAParameterType() throws NoSuchMethodException {
8587

8688
var method = Sample.class.getMethod("method", ScrollPosition.class, String.class);
87-
var parameters = new DefaultParameters(method);
89+
var parameters = new DefaultParameters(ParametersSource.of(method));
8890

8991
var accessor = new ParametersParameterAccessor(parameters, new Object[] { ScrollPosition.offset(1), "Foo" });
9092

@@ -96,7 +98,7 @@ void handlesScrollPositionAsAParameterType() throws NoSuchMethodException {
9698
void handlesPageRequestAsAParameterType() throws NoSuchMethodException {
9799

98100
var method = Sample.class.getMethod("methodWithPageRequest", PageRequest.class, String.class);
99-
var parameters = new DefaultParameters(method);
101+
var parameters = new DefaultParameters(ParametersSource.of(method));
100102

101103
var accessor = new ParametersParameterAccessor(parameters, new Object[] { PageRequest.of(0, 10), "Foo" });
102104

@@ -108,7 +110,7 @@ void handlesPageRequestAsAParameterType() throws NoSuchMethodException {
108110
void handlesLimitAsAParameterType() throws NoSuchMethodException {
109111

110112
var method = Sample.class.getMethod("method", Limit.class, String.class);
111-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
113+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
112114
new Object[] { Limit.of(100), "spring" });
113115

114116
assertThat(accessor).hasSize(1);
@@ -119,7 +121,7 @@ void handlesLimitAsAParameterType() throws NoSuchMethodException {
119121
void returnsLimitIfAvailable() throws NoSuchMethodException {
120122

121123
var method = Sample.class.getMethod("method", Limit.class, String.class);
122-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
124+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
123125
new Object[] { Limit.of(100), "spring" });
124126

125127
assertThat(accessor.getLimit()).extracting(Limit::max).isEqualTo(100);
@@ -129,7 +131,7 @@ void returnsLimitIfAvailable() throws NoSuchMethodException {
129131
void readsLimitFromPageableIfAvailable() throws NoSuchMethodException {
130132

131133
var method = Sample.class.getMethod("method", Pageable.class, String.class);
132-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
134+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
133135
new Object[] { Pageable.ofSize(100), "spring" });
134136

135137
assertThat(accessor.getLimit()).extracting(Limit::max).isEqualTo(100);
@@ -139,7 +141,7 @@ void readsLimitFromPageableIfAvailable() throws NoSuchMethodException {
139141
void returnsUnlimitedIfNoLimitingAvailable() throws NoSuchMethodException {
140142

141143
var method = Sample.class.getMethod("method", Sort.class, String.class);
142-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
144+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
143145
new Object[] { Pageable.ofSize(100), "spring" });
144146

145147
assertThat(accessor.getLimit().isUnlimited()).isTrue();
@@ -149,7 +151,7 @@ void returnsUnlimitedIfNoLimitingAvailable() throws NoSuchMethodException {
149151
void appliesLimitToPageableIfAvailable() throws NoSuchMethodException {
150152

151153
var method = Sample.class.getMethod("method", Limit.class, String.class);
152-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
154+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
153155
new Object[] { Limit.of(100), "spring" });
154156

155157
Pageable pageable = accessor.getPageable();
@@ -161,7 +163,7 @@ void appliesLimitToPageableIfAvailable() throws NoSuchMethodException {
161163
void appliesLimitToPageableIfRequested() throws NoSuchMethodException {
162164

163165
var method = Sample.class.getMethod("method", Limit.class, String.class);
164-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
166+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
165167
new Object[] { Limit.of(100), "spring" });
166168

167169
assertThat(accessor).hasSize(1);
@@ -172,7 +174,7 @@ void appliesLimitToPageableIfRequested() throws NoSuchMethodException {
172174
void appliesSortToPageableIfAvailable() throws NoSuchMethodException {
173175

174176
var method = Sample.class.getMethod("method", Sort.class, String.class);
175-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
177+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
176178
new Object[] { Sort.by("one", "two"), "spring" });
177179

178180
Pageable pageable = accessor.getPageable();
@@ -184,7 +186,7 @@ void appliesSortToPageableIfAvailable() throws NoSuchMethodException {
184186
void appliesSortAndLimitToPageableIfAvailable() throws NoSuchMethodException {
185187

186188
var method = Sample.class.getMethod("method", Sort.class, Limit.class, String.class);
187-
var accessor = new ParametersParameterAccessor(new DefaultParameters(method),
189+
var accessor = new ParametersParameterAccessor(new DefaultParameters(ParametersSource.of(method)),
188190
new Object[] { Sort.by("one", "two"), Limit.of(42), "spring" });
189191

190192
Pageable pageable = accessor.getPageable();

0 commit comments

Comments
 (0)