1
16 package org.springframework.data.repository.core.support;
17
18 import kotlin.coroutines.Continuation;
19 import kotlin.reflect.KFunction;
20 import kotlinx.coroutines.reactive.AwaitKt;
21
22 import java.lang.reflect.Method;
23
24 import org.reactivestreams.Publisher;
25
26 import org.springframework.core.KotlinDetector;
27 import org.springframework.data.repository.util.ReactiveWrapperConverters;
28 import org.springframework.data.util.KotlinReflectionUtils;
29 import org.springframework.data.util.ReflectionUtils;
30 import org.springframework.lang.Nullable;
31
32
40 class ImplementationInvocationMetadata {
41
42 private final boolean suspendedDeclaredMethod;
43 private final boolean suspendedBaseClassMethod;
44 private final boolean reactiveBaseClassMethod;
45
46 ImplementationInvocationMetadata(Method declaredMethod, Method baseClassMethod) {
47
48 if (!KotlinDetector.isKotlinReflectPresent()) {
49 suspendedDeclaredMethod = false;
50 suspendedBaseClassMethod = false;
51 reactiveBaseClassMethod = false;
52 return;
53 }
54
55 KFunction<?> declaredFunction = KotlinDetector.isKotlinType(declaredMethod.getDeclaringClass())
56 ? KotlinReflectionUtils.findKotlinFunction(declaredMethod)
57 : null;
58 KFunction<?> baseClassFunction = KotlinDetector.isKotlinType(baseClassMethod.getDeclaringClass())
59 ? KotlinReflectionUtils.findKotlinFunction(baseClassMethod)
60 : null;
61
62 suspendedDeclaredMethod = declaredFunction != null && declaredFunction.isSuspend();
63 suspendedBaseClassMethod = baseClassFunction != null && baseClassFunction.isSuspend();
64 this.reactiveBaseClassMethod = !suspendedBaseClassMethod
65 && ReactiveWrapperConverters.supports(baseClassMethod.getReturnType());
66 }
67
68 @Nullable
69 public Object invoke(Method methodToCall, Object instance, Object[] args) throws Throwable {
70
71 return shouldAdaptReactiveToSuspended() ? invokeReactiveToSuspend(methodToCall, instance, args)
72 : methodToCall.invoke(instance, args);
73
74 }
75
76 private boolean shouldAdaptReactiveToSuspended() {
77 return suspendedDeclaredMethod && !suspendedBaseClassMethod && reactiveBaseClassMethod;
78 }
79
80 @Nullable
81 @SuppressWarnings({ "unchecked", "ConstantConditions" })
82 private Object invokeReactiveToSuspend(Method methodToCall, Object instance, Object[] args)
83 throws ReflectiveOperationException {
84
85
90 Object[] invocationArguments = new Object[args.length - 1];
91 System.arraycopy(args, 0, invocationArguments, 0, invocationArguments.length);
92 Object result = methodToCall.invoke(instance, invocationArguments);
93
94 Publisher<?> publisher = result instanceof Publisher ? (Publisher<?>) result
95 : ReactiveWrapperConverters.toWrapper(result, Publisher.class);
96
97 return AwaitKt.awaitFirstOrNull(publisher, (Continuation) args[args.length - 1]);
98 }
99
100 boolean canInvoke(Method invokedMethod, Method backendMethod) {
101
102 if (suspendedDeclaredMethod == suspendedBaseClassMethod) {
103 return invokedMethod.getParameterCount() == backendMethod.getParameterCount();
104 }
105
106 if (suspendedDeclaredMethod && reactiveBaseClassMethod) {
107 return invokedMethod.getParameterCount() - 1 == backendMethod.getParameterCount();
108 }
109
110 return false;
111 }
112 }
113