1515 */
1616package org .springframework .data .repository .core .support ;
1717
18- import java .lang .reflect .Method ;
1918import java .util .Collection ;
2019import java .util .Collections ;
2120import java .util .HashMap ;
2221import java .util .Map ;
2322import java .util .Optional ;
2423
2524import org .springframework .core .CollectionFactory ;
25+ import org .springframework .core .KotlinDetector ;
2626import org .springframework .core .MethodParameter ;
2727import org .springframework .core .convert .ConversionService ;
2828import org .springframework .core .convert .TypeDescriptor ;
2929import org .springframework .core .convert .support .GenericConversionService ;
30+ import org .springframework .data .repository .util .ClassUtils ;
3031import org .springframework .data .repository .util .QueryExecutionConverters ;
3132import org .springframework .data .repository .util .ReactiveWrapperConverters ;
3233import org .springframework .data .util .NullableWrapper ;
34+ import org .springframework .data .util .ReactiveWrappers ;
3335import org .springframework .data .util .Streamable ;
3436import org .springframework .lang .Nullable ;
3537
@@ -44,12 +46,14 @@ class QueryExecutionResultHandler {
4446
4547 private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor .valueOf (NullableWrapper .class );
4648
49+ private static final Class <?> FLOW_TYPE = loadIfPresent ("kotlinx.coroutines.flow.Flow" );
50+
4751 private final GenericConversionService conversionService ;
4852
4953 private final Object mutex = new Object ();
5054
5155 // concurrent access guarded by mutex.
52- private Map <Method , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
56+ private Map <MethodParameter , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
5357
5458 /**
5559 * Creates a new {@link QueryExecutionResultHandler}.
@@ -58,6 +62,17 @@ class QueryExecutionResultHandler {
5862 this .conversionService = conversionService ;
5963 }
6064
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+
6176 /**
6277 * Post-processes the given result of a query invocation to match the return type of the given method.
6378 *
@@ -66,9 +81,9 @@ class QueryExecutionResultHandler {
6681 * @return
6782 */
6883 @ Nullable
69- Object postProcessInvocationResult (@ Nullable Object result , Method method ) {
84+ Object postProcessInvocationResult (@ Nullable Object result , MethodParameter method ) {
7085
71- if (!processingRequired (result , method . getReturnType () )) {
86+ if (!processingRequired (result , method )) {
7287 return result ;
7388 }
7489
@@ -77,24 +92,23 @@ Object postProcessInvocationResult(@Nullable Object result, Method method) {
7792 return postProcessInvocationResult (result , 0 , descriptor );
7893 }
7994
80- private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor (Method method ) {
95+ private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor (MethodParameter method ) {
8196
82- Map <Method , ReturnTypeDescriptor > descriptorCache = this .descriptorCache ;
97+ Map <MethodParameter , ReturnTypeDescriptor > descriptorCache = this .descriptorCache ;
8398 ReturnTypeDescriptor descriptor = descriptorCache .get (method );
8499
85100 if (descriptor == null ) {
86101
87102 descriptor = ReturnTypeDescriptor .of (method );
88103
89- Map <Method , ReturnTypeDescriptor > updatedDescriptorCache ;
104+ Map <MethodParameter , ReturnTypeDescriptor > updatedDescriptorCache ;
90105
91106 if (descriptorCache .isEmpty ()) {
92107 updatedDescriptorCache = Collections .singletonMap (method , descriptor );
93108 } else {
94109 updatedDescriptorCache = new HashMap <>(descriptorCache .size () + 1 , 1 );
95110 updatedDescriptorCache .putAll (descriptorCache );
96111 updatedDescriptorCache .put (method , descriptor );
97-
98112 }
99113
100114 synchronized (mutex ) {
@@ -234,10 +248,21 @@ private static Object unwrapOptional(@Nullable Object source) {
234248 * Returns whether we have to process the given source object in the first place.
235249 *
236250 * @param source can be {@literal null}.
237- * @param targetType must not be {@literal null}.
251+ * @param methodParameter must not be {@literal null}.
238252 * @return
239253 */
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+ }
241266
242267 return !targetType .isInstance (source ) //
243268 || source == null //
@@ -253,19 +278,19 @@ static class ReturnTypeDescriptor {
253278 private final TypeDescriptor typeDescriptor ;
254279 private final @ Nullable TypeDescriptor nestedTypeDescriptor ;
255280
256- private ReturnTypeDescriptor (Method method ) {
257- this .methodParameter = new MethodParameter ( method , - 1 ) ;
281+ private ReturnTypeDescriptor (MethodParameter methodParameter ) {
282+ this .methodParameter = methodParameter ;
258283 this .typeDescriptor = TypeDescriptor .nested (this .methodParameter , 0 );
259284 this .nestedTypeDescriptor = TypeDescriptor .nested (this .methodParameter , 1 );
260285 }
261286
262287 /**
263- * Create a {@link ReturnTypeDescriptor} from a {@link Method }.
288+ * Create a {@link ReturnTypeDescriptor} from a {@link MethodParameter }.
264289 *
265290 * @param method
266291 * @return
267292 */
268- public static ReturnTypeDescriptor of (Method method ) {
293+ public static ReturnTypeDescriptor of (MethodParameter method ) {
269294 return new ReturnTypeDescriptor (method );
270295 }
271296
0 commit comments