1
16 package org.springframework.data.repository.core.support;
17
18 import java.lang.reflect.Method;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Optional;
24
25 import org.springframework.core.CollectionFactory;
26 import org.springframework.core.MethodParameter;
27 import org.springframework.core.convert.ConversionService;
28 import org.springframework.core.convert.TypeDescriptor;
29 import org.springframework.core.convert.support.GenericConversionService;
30 import org.springframework.data.repository.util.NullableWrapper;
31 import org.springframework.data.repository.util.QueryExecutionConverters;
32 import org.springframework.data.repository.util.ReactiveWrapperConverters;
33 import org.springframework.data.util.Streamable;
34 import org.springframework.lang.Nullable;
35
36
43 class QueryExecutionResultHandler {
44
45 private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class);
46
47 private final GenericConversionService conversionService;
48
49 private final Object mutex = new Object();
50
51
52 private Map<Method, ReturnTypeDescriptor> descriptorCache = Collections.emptyMap();
53
54
57 public QueryExecutionResultHandler(GenericConversionService conversionService) {
58 this.conversionService = conversionService;
59 }
60
61
68 @Nullable
69 public Object postProcessInvocationResult(@Nullable Object result, Method method) {
70
71 if (!processingRequired(result, method.getReturnType())) {
72 return result;
73 }
74
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) {
86
87 descriptor = ReturnTypeDescriptor.of(method);
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;
106 }
107
108
116 @Nullable
117 Object postProcessInvocationResult(@Nullable Object result, int nestingLevel, ReturnTypeDescriptor descriptor) {
118
119 TypeDescriptor returnTypeDescriptor = descriptor.getReturnTypeDescriptor(nestingLevel);
120
121 if (returnTypeDescriptor == null) {
122 return result;
123 }
124
125 Class<?> expectedReturnType = returnTypeDescriptor.getType();
126
127 result = unwrapOptional(result);
128
129 if (QueryExecutionConverters.supports(expectedReturnType)) {
130
131
132 result = postProcessInvocationResult(result, nestingLevel + 1, descriptor);
133
134 if (conversionRequired(WRAPPER_TYPE, returnTypeDescriptor)) {
135 return conversionService.convert(new NullableWrapper(result), returnTypeDescriptor);
136 }
137
138 if (result != null) {
139
140 TypeDescriptor source = TypeDescriptor.valueOf(result.getClass());
141
142 if (conversionRequired(source, returnTypeDescriptor)) {
143 return conversionService.convert(result, returnTypeDescriptor);
144 }
145 }
146 }
147
148 if (result != null) {
149
150 if (ReactiveWrapperConverters.supports(expectedReturnType)) {
151 return ReactiveWrapperConverters.toWrapper(result, expectedReturnType);
152 }
153
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)
166 ? conversionService.convert(result, returnTypeDescriptor)
167 : result;
168 }
169
170 return Map.class.equals(expectedReturnType)
171 ? CollectionFactory.createMap(expectedReturnType, 0)
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;
196 }
197
198
206 private boolean conversionRequired(TypeDescriptor source, TypeDescriptor target) {
207
208 return conversionService.canConvert(source, target)
209 && !conversionService.canBypassConvert(source, target);
210 }
211
212
218 @Nullable
219 @SuppressWarnings("unchecked")
220 private static Object unwrapOptional(@Nullable Object source) {
221
222 if (source == null) {
223 return null;
224 }
225
226 return Optional.class.isInstance(source)
227 ? Optional.class.cast(source).orElse(null)
228 : source;
229 }
230
231
238 private static boolean processingRequired(@Nullable Object source, Class<?> targetType) {
239
240 return !targetType.isInstance(source)
241 || source == null
242 || Collection.class.isInstance(source);
243 }
244
245
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
266 public static ReturnTypeDescriptor of(Method method) {
267 return new ReturnTypeDescriptor(method);
268 }
269
270
278 @Nullable
279 public TypeDescriptor getReturnTypeDescriptor(int nestingLevel) {
280
281
282
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 }
294 }
295