17
17
18
18
import java .lang .reflect .Method ;
19
19
import java .util .Collection ;
20
+ import java .util .Collections ;
21
+ import java .util .HashMap ;
20
22
import java .util .Map ;
21
23
import java .util .Optional ;
22
24
23
25
import org .springframework .core .CollectionFactory ;
24
26
import org .springframework .core .MethodParameter ;
25
27
import org .springframework .core .convert .ConversionService ;
26
28
import org .springframework .core .convert .TypeDescriptor ;
27
- import org .springframework .core .convert .support .DefaultConversionService ;
28
29
import org .springframework .core .convert .support .GenericConversionService ;
29
30
import org .springframework .data .repository .util .NullableWrapper ;
30
31
import org .springframework .data .repository .util .QueryExecutionConverters ;
31
32
import org .springframework .data .repository .util .ReactiveWrapperConverters ;
33
+ import org .springframework .data .util .Streamable ;
32
34
import org .springframework .lang .Nullable ;
33
35
34
36
/**
@@ -44,15 +46,15 @@ class QueryExecutionResultHandler {
44
46
45
47
private final GenericConversionService conversionService ;
46
48
49
+ private final Object mutex = new Object ();
50
+
51
+ // concurrent access guarded by mutex.
52
+ private Map <Method , ReturnTypeDescriptor > descriptorCache = Collections .emptyMap ();
53
+
47
54
/**
48
55
* Creates a new {@link QueryExecutionResultHandler}.
49
56
*/
50
- public QueryExecutionResultHandler () {
51
-
52
- GenericConversionService conversionService = new DefaultConversionService ();
53
- QueryExecutionConverters .registerConvertersIn (conversionService );
54
- conversionService .removeConvertible (Object .class , Object .class );
55
-
57
+ public QueryExecutionResultHandler (GenericConversionService conversionService ) {
56
58
this .conversionService = conversionService ;
57
59
}
58
60
@@ -70,24 +72,51 @@ public Object postProcessInvocationResult(@Nullable Object result, Method method
70
72
return result ;
71
73
}
72
74
73
- MethodParameter parameter = new MethodParameter (method , -1 );
75
+ ReturnTypeDescriptor descriptor = getOrCreateReturnTypeDescriptor (method );
76
+
77
+ return postProcessInvocationResult (result , 0 , descriptor );
78
+ }
79
+
80
+ private ReturnTypeDescriptor getOrCreateReturnTypeDescriptor (Method method ) {
81
+
82
+ Map <Method , ReturnTypeDescriptor > descriptorCache = this .descriptorCache ;
83
+ ReturnTypeDescriptor descriptor = descriptorCache .get (method );
84
+
85
+ if (descriptor == null ) {
74
86
75
- return postProcessInvocationResult ( result , 0 , parameter );
87
+ descriptor = ReturnTypeDescriptor . of ( method );
76
88
89
+ Map <Method , ReturnTypeDescriptor > updatedDescriptorCache ;
90
+
91
+ if (descriptorCache .isEmpty ()) {
92
+ updatedDescriptorCache = Collections .singletonMap (method , descriptor );
93
+ } else {
94
+ updatedDescriptorCache = new HashMap <>(descriptorCache .size () + 1 , 1 );
95
+ updatedDescriptorCache .putAll (descriptorCache );
96
+ updatedDescriptorCache .put (method , descriptor );
97
+
98
+ }
99
+
100
+ synchronized (mutex ) {
101
+ this .descriptorCache = updatedDescriptorCache ;
102
+ }
103
+ }
104
+
105
+ return descriptor ;
77
106
}
78
107
79
108
/**
80
109
* Post-processes the given result of a query invocation to the given type.
81
110
*
82
111
* @param result can be {@literal null}.
83
112
* @param nestingLevel
84
- * @param parameter must not be {@literal null}.
113
+ * @param descriptor must not be {@literal null}.
85
114
* @return
86
115
*/
87
116
@ Nullable
88
- Object postProcessInvocationResult (@ Nullable Object result , int nestingLevel , MethodParameter parameter ) {
117
+ Object postProcessInvocationResult (@ Nullable Object result , int nestingLevel , ReturnTypeDescriptor descriptor ) {
89
118
90
- TypeDescriptor returnTypeDescriptor = TypeDescriptor . nested ( parameter , nestingLevel );
119
+ TypeDescriptor returnTypeDescriptor = descriptor . getReturnTypeDescriptor ( nestingLevel );
91
120
92
121
if (returnTypeDescriptor == null ) {
93
122
return result ;
@@ -100,7 +129,7 @@ Object postProcessInvocationResult(@Nullable Object result, int nestingLevel, Me
100
129
if (QueryExecutionConverters .supports (expectedReturnType )) {
101
130
102
131
// For a wrapper type, try nested resolution first
103
- result = postProcessInvocationResult (result , nestingLevel + 1 , parameter );
132
+ result = postProcessInvocationResult (result , nestingLevel + 1 , descriptor );
104
133
105
134
if (conversionRequired (WRAPPER_TYPE , returnTypeDescriptor )) {
106
135
return conversionService .convert (new NullableWrapper (result ), returnTypeDescriptor );
@@ -122,14 +151,48 @@ Object postProcessInvocationResult(@Nullable Object result, int nestingLevel, Me
122
151
return ReactiveWrapperConverters .toWrapper (result , expectedReturnType );
123
152
}
124
153
125
- return conversionService .canConvert (TypeDescriptor .forObject (result ), returnTypeDescriptor )
154
+ if (result instanceof Collection <?>) {
155
+
156
+ TypeDescriptor elementDescriptor = descriptor .getReturnTypeDescriptor (nestingLevel + 1 );
157
+ boolean requiresConversion = requiresConversion ((Collection <?>) result , expectedReturnType , elementDescriptor );
158
+
159
+ if (!requiresConversion ) {
160
+ return result ;
161
+ }
162
+ }
163
+
164
+ TypeDescriptor resultDescriptor = TypeDescriptor .forObject (result );
165
+ return conversionService .canConvert (resultDescriptor , returnTypeDescriptor )
126
166
? conversionService .convert (result , returnTypeDescriptor )
127
167
: result ;
128
168
}
129
169
130
170
return Map .class .equals (expectedReturnType ) //
131
171
? CollectionFactory .createMap (expectedReturnType , 0 ) //
132
172
: null ;
173
+
174
+ }
175
+ private boolean requiresConversion (Collection <?> collection , Class <?> expectedReturnType ,
176
+ @ Nullable TypeDescriptor elementDescriptor ) {
177
+
178
+ if (Streamable .class .isAssignableFrom (expectedReturnType ) || !expectedReturnType .isInstance (collection )) {
179
+ return true ;
180
+ }
181
+
182
+ if (elementDescriptor == null || !Iterable .class .isAssignableFrom (expectedReturnType )) {
183
+ return false ;
184
+ }
185
+
186
+ Class <?> type = elementDescriptor .getType ();
187
+
188
+ for (Object o : collection ) {
189
+
190
+ if (!type .isInstance (o )) {
191
+ return true ;
192
+ }
193
+ }
194
+
195
+ return false ;
133
196
}
134
197
135
198
/**
@@ -178,4 +241,54 @@ private static boolean processingRequired(@Nullable Object source, Class<?> targ
178
241
|| source == null //
179
242
|| Collection .class .isInstance (source );
180
243
}
244
+
245
+ /**
246
+ * Value object capturing {@link MethodParameter} and {@link TypeDescriptor}s for top and nested levels.
247
+ */
248
+ static class ReturnTypeDescriptor {
249
+
250
+ private final MethodParameter methodParameter ;
251
+ private final TypeDescriptor typeDescriptor ;
252
+ private final @ Nullable TypeDescriptor nestedTypeDescriptor ;
253
+
254
+ private ReturnTypeDescriptor (Method method ) {
255
+ this .methodParameter = new MethodParameter (method , -1 );
256
+ this .typeDescriptor = TypeDescriptor .nested (this .methodParameter , 0 );
257
+ this .nestedTypeDescriptor = TypeDescriptor .nested (this .methodParameter , 1 );
258
+ }
259
+
260
+ /**
261
+ * Create a {@link ReturnTypeDescriptor} from a {@link Method}.
262
+ *
263
+ * @param method
264
+ * @return
265
+ */
266
+ public static ReturnTypeDescriptor of (Method method ) {
267
+ return new ReturnTypeDescriptor (method );
268
+ }
269
+
270
+ /**
271
+ * Return the {@link TypeDescriptor} for a nested type declared within the method parameter described by
272
+ * {@code nestingLevel} .
273
+ *
274
+ * @param nestingLevel the nesting level. {@code 0} is the first level, {@code 1} the next inner one.
275
+ * @return the {@link TypeDescriptor} or {@literal null} if it could not be obtained.
276
+ * @see TypeDescriptor#nested(MethodParameter, int)
277
+ */
278
+ @ Nullable
279
+ public TypeDescriptor getReturnTypeDescriptor (int nestingLevel ) {
280
+
281
+ // optimizing for nesting level 0 and 1 (Optional<T>, List<T>)
282
+ // nesting level 2 (Optional<List<T>>) uses the slow path.
283
+
284
+ switch (nestingLevel ) {
285
+ case 0 :
286
+ return typeDescriptor ;
287
+ case 1 :
288
+ return nestedTypeDescriptor ;
289
+ default :
290
+ return TypeDescriptor .nested (this .methodParameter , nestingLevel );
291
+ }
292
+ }
293
+ }
181
294
}
0 commit comments