15
15
*/
16
16
package org .springframework .data .repository .core .support ;
17
17
18
- import java .lang .reflect .Method ;
19
18
import java .util .Collection ;
20
19
import java .util .Collections ;
21
20
import java .util .HashMap ;
22
21
import java .util .Map ;
23
22
import java .util .Optional ;
24
23
25
24
import org .springframework .core .CollectionFactory ;
25
+ import org .springframework .core .KotlinDetector ;
26
26
import org .springframework .core .MethodParameter ;
27
27
import org .springframework .core .convert .ConversionService ;
28
28
import org .springframework .core .convert .TypeDescriptor ;
29
29
import org .springframework .core .convert .support .GenericConversionService ;
30
+ import org .springframework .data .repository .util .ClassUtils ;
30
31
import org .springframework .data .repository .util .QueryExecutionConverters ;
31
32
import org .springframework .data .repository .util .ReactiveWrapperConverters ;
32
33
import org .springframework .data .util .NullableWrapper ;
34
+ import org .springframework .data .util .ReactiveWrappers ;
33
35
import org .springframework .data .util .Streamable ;
34
36
import org .springframework .lang .Nullable ;
35
37
@@ -44,12 +46,14 @@ class QueryExecutionResultHandler {
44
46
45
47
private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor .valueOf (NullableWrapper .class );
46
48
49
+ private static final Class <?> FLOW_TYPE = loadIfPresent ("kotlinx.coroutines.flow.Flow" );
50
+
47
51
private final GenericConversionService conversionService ;
48
52
49
53
private final Object mutex = new Object ();
50
54
51
55
// concurrent access guarded by mutex.
52
- private Map <Method , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
56
+ private Map <MethodParameter , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
53
57
54
58
/**
55
59
* Creates a new {@link QueryExecutionResultHandler}.
@@ -58,6 +62,17 @@ class QueryExecutionResultHandler {
58
62
this .conversionService = conversionService ;
59
63
}
60
64
65
+ @ Nullable
66
+ @ SuppressWarnings ("unchecked" )
67
+ public static <T > Class <T > loadIfPresent (String type ) {
68
+
69
+ try {
70
+ return (Class <T >) org .springframework .util .ClassUtils .forName (type , ClassUtils .class .getClassLoader ());
71
+ } catch (ClassNotFoundException | LinkageError e ) {
72
+ return null ;
73
+ }
74
+ }
75
+
61
76
/**
62
77
* Post-processes the given result of a query invocation to match the return type of the given method.
63
78
*
@@ -66,9 +81,9 @@ class QueryExecutionResultHandler {
66
81
* @return
67
82
*/
68
83
@ Nullable
69
- Object postProcessInvocationResult (@ Nullable Object result , Method method ) {
84
+ Object postProcessInvocationResult (@ Nullable Object result , MethodParameter method ) {
70
85
71
- if (!processingRequired (result , method . getReturnType () )) {
86
+ if (!processingRequired (result , method )) {
72
87
return result ;
73
88
}
74
89
@@ -77,24 +92,23 @@ Object postProcessInvocationResult(@Nullable Object result, Method method) {
77
92
return postProcessInvocationResult (result , 0 , descriptor );
78
93
}
79
94
80
- private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor (Method method ) {
95
+ private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor (MethodParameter method ) {
81
96
82
- Map <Method , ReturnTypeDescriptor > descriptorCache = this .descriptorCache ;
97
+ Map <MethodParameter , ReturnTypeDescriptor > descriptorCache = this .descriptorCache ;
83
98
ReturnTypeDescriptor descriptor = descriptorCache .get (method );
84
99
85
100
if (descriptor == null ) {
86
101
87
102
descriptor = ReturnTypeDescriptor .of (method );
88
103
89
- Map <Method , ReturnTypeDescriptor > updatedDescriptorCache ;
104
+ Map <MethodParameter , ReturnTypeDescriptor > updatedDescriptorCache ;
90
105
91
106
if (descriptorCache .isEmpty ()) {
92
107
updatedDescriptorCache = Collections .singletonMap (method , descriptor );
93
108
} else {
94
109
updatedDescriptorCache = new HashMap <>(descriptorCache .size () + 1 , 1 );
95
110
updatedDescriptorCache .putAll (descriptorCache );
96
111
updatedDescriptorCache .put (method , descriptor );
97
-
98
112
}
99
113
100
114
synchronized (mutex ) {
@@ -234,10 +248,21 @@ private static Object unwrapOptional(@Nullable Object source) {
234
248
* Returns whether we have to process the given source object in the first place.
235
249
*
236
250
* @param source can be {@literal null}.
237
- * @param targetType must not be {@literal null}.
251
+ * @param methodParameter must not be {@literal null}.
238
252
* @return
239
253
*/
240
- private static boolean processingRequired (@ Nullable Object source , Class <?> targetType ) {
254
+ private static boolean processingRequired (@ Nullable Object source , MethodParameter methodParameter ) {
255
+
256
+ Class <?> targetType = methodParameter .getParameterType ();
257
+
258
+ if (source != null && ReactiveWrappers .KOTLIN_COROUTINES_PRESENT
259
+ && KotlinDetector .isSuspendingFunction (methodParameter .getMethod ())) {
260
+
261
+ // Spring's AOP invoker handles Publisher to Flow conversion, so we have to exempt these from post-processing.
262
+ if (FLOW_TYPE != null && FLOW_TYPE .isAssignableFrom (targetType )) {
263
+ return false ;
264
+ }
265
+ }
241
266
242
267
return !targetType .isInstance (source ) //
243
268
|| source == null //
@@ -253,19 +278,19 @@ static class ReturnTypeDescriptor {
253
278
private final TypeDescriptor typeDescriptor ;
254
279
private final @ Nullable TypeDescriptor nestedTypeDescriptor ;
255
280
256
- private ReturnTypeDescriptor (Method method ) {
257
- this .methodParameter = new MethodParameter ( method , - 1 ) ;
281
+ private ReturnTypeDescriptor (MethodParameter methodParameter ) {
282
+ this .methodParameter = methodParameter ;
258
283
this .typeDescriptor = TypeDescriptor .nested (this .methodParameter , 0 );
259
284
this .nestedTypeDescriptor = TypeDescriptor .nested (this .methodParameter , 1 );
260
285
}
261
286
262
287
/**
263
- * Create a {@link ReturnTypeDescriptor} from a {@link Method }.
288
+ * Create a {@link ReturnTypeDescriptor} from a {@link MethodParameter }.
264
289
*
265
290
* @param method
266
291
* @return
267
292
*/
268
- public static ReturnTypeDescriptor of (Method method ) {
293
+ public static ReturnTypeDescriptor of (MethodParameter method ) {
269
294
return new ReturnTypeDescriptor (method );
270
295
}
271
296
0 commit comments