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 ;
3030import org .springframework .data .repository .util .QueryExecutionConverters ;
3131import org .springframework .data .repository .util .ReactiveWrapperConverters ;
3232import org .springframework .data .util .NullableWrapper ;
33+ import org .springframework .data .util .ReactiveWrappers ;
3334import org .springframework .data .util .Streamable ;
3435import org .springframework .lang .Nullable ;
3536
@@ -44,12 +45,14 @@ class QueryExecutionResultHandler {
4445
4546 private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor .valueOf (NullableWrapper .class );
4647
48+ private static final Class <?> FLOW_TYPE = loadIfPresent ("kotlinx.coroutines.flow.Flow" );
49+
4750 private final GenericConversionService conversionService ;
4851
4952 private final Object mutex = new Object ();
5053
5154 // concurrent access guarded by mutex.
52- private Map <Method , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
55+ private Map <MethodParameter , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
5356
5457 /**
5558 * Creates a new {@link QueryExecutionResultHandler}.
@@ -58,6 +61,18 @@ class QueryExecutionResultHandler {
5861 this .conversionService = conversionService ;
5962 }
6063
64+ @ Nullable
65+ @ SuppressWarnings ("unchecked" )
66+ public static <T > Class <T > loadIfPresent (String type ) {
67+
68+ try {
69+ return (Class <T >) org .springframework .util .ClassUtils .forName (type ,
70+ org .springframework .hateoas .support .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