1
16 package org.springframework.data.repository.core.support;
17
18 import static org.springframework.core.GenericTypeResolver.*;
19
20 import lombok.Value;
21
22 import java.lang.reflect.GenericDeclaration;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Type;
25 import java.lang.reflect.TypeVariable;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.function.Predicate;
32 import java.util.function.Supplier;
33 import java.util.stream.IntStream;
34 import java.util.stream.Stream;
35
36 import org.springframework.core.MethodParameter;
37 import org.springframework.core.ResolvableType;
38 import org.springframework.data.repository.Repository;
39 import org.springframework.data.repository.core.RepositoryMetadata;
40 import org.springframework.data.repository.core.support.MethodLookup.MethodPredicate;
41 import org.springframework.data.repository.util.QueryExecutionConverters;
42 import org.springframework.data.repository.util.ReactiveWrapperConverters;
43 import org.springframework.util.Assert;
44
45
52 interface MethodLookups {
53
54
59 public static MethodLookup direct() {
60
61 MethodPredicate direct = (invoked, candidate) -> candidate.getName().equals(invoked.getName())
62 && candidate.getParameterCount() == invoked.getParameterCount()
63 && Arrays.equals(candidate.getParameterTypes(), invoked.getParameterTypes());
64
65 return () -> Collections.singletonList(direct);
66 }
67
68
78 public static MethodLookup forRepositoryTypes(RepositoryMetadata repositoryMetadata) {
79 return direct().and(new RepositoryAwareMethodLookup(repositoryMetadata));
80 }
81
82
95 public static MethodLookup forReactiveTypes(RepositoryMetadata repositoryMetadata) {
96 return direct().and(new ReactiveTypeInteropMethodLookup(repositoryMetadata));
97 }
98
99
105 static class RepositoryAwareMethodLookup implements MethodLookup {
106
107 @SuppressWarnings("rawtypes") private static final TypeVariable<Class<Repository>>[] PARAMETERS = Repository.class
108 .getTypeParameters();
109 private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName();
110 private static final String ID_TYPE_NAME = PARAMETERS[1].getName();
111
112 private final ResolvableType entityType, idType;
113 private final Class<?> repositoryInterface;
114
115
120 public RepositoryAwareMethodLookup(RepositoryMetadata repositoryMetadata) {
121
122 Assert.notNull(repositoryMetadata, "Repository metadata must not be null!");
123
124 this.entityType = ResolvableType.forClass(repositoryMetadata.getDomainType());
125 this.idType = ResolvableType.forClass(repositoryMetadata.getIdType());
126 this.repositoryInterface = repositoryMetadata.getRepositoryInterface();
127 }
128
129
133 @Override
134 public List<MethodPredicate> getLookups() {
135
136 MethodPredicate detailedComparison = (invoked, candidate) -> Optional.of(candidate)
137 .filter(baseClassMethod -> baseClassMethod.getName().equals(invoked.getName()))
138 .filter(baseClassMethod -> baseClassMethod.getParameterCount() == invoked.getParameterCount())
139 .filter(baseClassMethod -> parametersMatch(invoked.getMethod(), baseClassMethod))
140 .isPresent();
141
142 return Collections.singletonList(detailedComparison);
143 }
144
145
154 protected boolean matchesGenericType(TypeVariable<?> variable, ResolvableType parameterType) {
155
156 GenericDeclaration declaration = variable.getGenericDeclaration();
157
158 if (declaration instanceof Class) {
159
160 if (ID_TYPE_NAME.equals(variable.getName()) && parameterType.isAssignableFrom(idType)) {
161 return true;
162 }
163
164 Type boundType = variable.getBounds()[0];
165 String referenceName = boundType instanceof TypeVariable ? boundType.toString() : variable.toString();
166
167 return DOMAIN_TYPE_NAME.equals(referenceName) && parameterType.isAssignableFrom(entityType);
168 }
169
170 for (Type type : variable.getBounds()) {
171 if (ResolvableType.forType(type).isAssignableFrom(parameterType)) {
172 return true;
173 }
174 }
175
176 return false;
177 }
178
179
187 private boolean parametersMatch(Method invokedMethod, Method candidate) {
188
189 Class<?>[] methodParameterTypes = invokedMethod.getParameterTypes();
190 Type[] genericTypes = candidate.getGenericParameterTypes();
191 Class<?>[] types = candidate.getParameterTypes();
192
193 for (int i = 0; i < genericTypes.length; i++) {
194
195 Type genericType = genericTypes[i];
196 Class<?> type = types[i];
197 MethodParameter parameter = new MethodParameter(invokedMethod, i);
198 Class<?> parameterType = resolveParameterType(parameter, repositoryInterface);
199
200 if (genericType instanceof TypeVariable<?>) {
201
202 if (!matchesGenericType((TypeVariable<?>) genericType, ResolvableType.forMethodParameter(parameter))) {
203 return false;
204 }
205
206 continue;
207 }
208
209 if (types[i].equals(parameterType)) {
210 continue;
211 }
212
213 if (!type.isAssignableFrom(parameterType) || !type.equals(methodParameterTypes[i])) {
214 return false;
215 }
216 }
217
218 return true;
219 }
220 }
221
222
228 static class ReactiveTypeInteropMethodLookup extends RepositoryAwareMethodLookup {
229
230 private final RepositoryMetadata repositoryMetadata;
231
232 public ReactiveTypeInteropMethodLookup(RepositoryMetadata repositoryMetadata) {
233
234 super(repositoryMetadata);
235 this.repositoryMetadata = repositoryMetadata;
236 }
237
238
242 @Override
243 public List<MethodPredicate> getLookups() {
244
245 MethodPredicate convertibleComparison = (invokedMethod, candidate) -> {
246
247 List<Supplier<Optional<Method>>> suppliers = new ArrayList<>();
248
249 if (usesParametersWithReactiveWrappers(invokedMethod.getMethod())) {
250 suppliers.add(() -> getMethodCandidate(invokedMethod, candidate, assignableWrapperMatch()));
251 suppliers.add(() -> getMethodCandidate(invokedMethod, candidate, wrapperConversionMatch()));
252 }
253
254 return suppliers.stream().anyMatch(supplier -> supplier.get().isPresent());
255 };
256
257 MethodPredicate detailedComparison = (invokedMethod, candidate) -> getMethodCandidate(invokedMethod, candidate,
258 matchParameterOrComponentType(repositoryMetadata.getRepositoryInterface())).isPresent();
259
260 return Arrays.asList(convertibleComparison, detailedComparison);
261 }
262
263
272 private Predicate<ParameterOverrideCriteria> matchParameterOrComponentType(Class<?> repositoryInterface) {
273
274 return (parameterCriteria) -> {
275
276 Class<?> parameterType = resolveParameterType(parameterCriteria.getDeclared(), repositoryInterface);
277 Type genericType = parameterCriteria.getGenericBaseType();
278
279 if (genericType instanceof TypeVariable<?>) {
280
281 if (!matchesGenericType((TypeVariable<?>) genericType,
282 ResolvableType.forMethodParameter(parameterCriteria.getDeclared()))) {
283 return false;
284 }
285 }
286
287 return parameterCriteria.getBaseType().isAssignableFrom(parameterType)
288 && parameterCriteria.isAssignableFromDeclared();
289 };
290 }
291
292
298 private static boolean isNonUnwrappingWrapper(Class<?> parameterType) {
299
300 Assert.notNull(parameterType, "Parameter type must not be null!");
301
302 return QueryExecutionConverters.supports(parameterType)
303 && !QueryExecutionConverters.supportsUnwrapping(parameterType);
304 }
305
306
312 private static boolean usesParametersWithReactiveWrappers(Method method) {
313
314 Assert.notNull(method, "Method must not be null!");
315
316 return Arrays.stream(method.getParameterTypes())
317 .anyMatch(ReactiveTypeInteropMethodLookup::isNonUnwrappingWrapper);
318 }
319
320
329 private static Optional<Method> getMethodCandidate(InvokedMethod invokedMethod, Method candidate,
330 Predicate<ParameterOverrideCriteria> predicate) {
331
332 return Optional.of(candidate)
333 .filter(it -> invokedMethod.getName().equals(it.getName()))
334 .filter(it -> parameterCountMatch(invokedMethod, it))
335 .filter(it -> parametersMatch(invokedMethod.getMethod(), it, predicate));
336 }
337
338
347 private static boolean parametersMatch(Method declaredMethod, Method baseClassMethod,
348 Predicate<ParameterOverrideCriteria> predicate) {
349 return methodParameters(declaredMethod, baseClassMethod).allMatch(predicate);
350 }
351
352
359 private static Predicate<ParameterOverrideCriteria> wrapperConversionMatch() {
360
361 return (parameterCriteria) -> isNonUnwrappingWrapper(parameterCriteria.getBaseType())
362 && isNonUnwrappingWrapper(parameterCriteria.getDeclaredType())
363 && ReactiveWrapperConverters.canConvert(parameterCriteria.getDeclaredType(), parameterCriteria.getBaseType());
364 }
365
366
373 private static Predicate<ParameterOverrideCriteria> assignableWrapperMatch() {
374
375 return (parameterCriteria) -> isNonUnwrappingWrapper(parameterCriteria.getBaseType())
376 && isNonUnwrappingWrapper(parameterCriteria.getDeclaredType())
377 && parameterCriteria.getBaseType().isAssignableFrom(parameterCriteria.getDeclaredType());
378 }
379
380 private static boolean parameterCountMatch(InvokedMethod invokedMethod, Method baseClassMethod) {
381
382 ImplementationInvocationMetadata invocationMetadata = new ImplementationInvocationMetadata(
383 invokedMethod.getMethod(), baseClassMethod);
384 return invocationMetadata.canInvoke(invokedMethod.getMethod(), baseClassMethod);
385 }
386
387 private static Stream<ParameterOverrideCriteria> methodParameters(Method invokedMethod, Method baseClassMethod) {
388 return IntStream.range(0, baseClassMethod.getParameterCount())
389 .mapToObj(index -> ParameterOverrideCriteria.of(new MethodParameter(invokedMethod, index),
390 new MethodParameter(baseClassMethod, index)));
391 }
392
393
398 @Value(staticConstructor = "of")
399 static class ParameterOverrideCriteria {
400
401 private final MethodParameter declared;
402 private final MethodParameter base;
403
404
407 public Class<?> getBaseType() {
408 return base.getParameterType();
409 }
410
411
414 public Type getGenericBaseType() {
415 return base.getGenericParameterType();
416 }
417
418
421 public Class<?> getDeclaredType() {
422 return declared.getParameterType();
423 }
424
425 public boolean isAssignableFromDeclared() {
426 return getBaseType().isAssignableFrom(getDeclaredType());
427 }
428 }
429 }
430 }
431