1 /*
2  * Copyright 2014-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

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 /**
37  * Simple domain service to convert query results into a dedicated type.
38  *
39  * @author Oliver Gierke
40  * @author Mark Paluch
41  * @author Jens Schauder
42  */

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     // concurrent access guarded by mutex.
52     private Map<Method, ReturnTypeDescriptor> descriptorCache = Collections.emptyMap();
53
54     /**
55      * Creates a new {@link QueryExecutionResultHandler}.
56      */

57     public QueryExecutionResultHandler(GenericConversionService conversionService) {
58         this.conversionService = conversionService;
59     }
60
61     /**
62      * Post-processes the given result of a query invocation to match the return type of the given method.
63      *
64      * @param result can be {@literal null}.
65      * @param method must not be {@literal null}.
66      * @return
67      */

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     /**
109      * Post-processes the given result of a query invocation to the given type.
110      *
111      * @param result can be {@literal null}.
112      * @param nestingLevel
113      * @param descriptor must not be {@literal null}.
114      * @return
115      */

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             // For a wrapper type, try nested resolution first
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     /**
199      * Returns whether the configured {@link ConversionService} can convert between the given {@link TypeDescriptor}s and
200      * the conversion will not be a no-op.
201      *
202      * @param source
203      * @param target
204      * @return
205      */

206     private boolean conversionRequired(TypeDescriptor source, TypeDescriptor target) {
207
208         return conversionService.canConvert(source, target) //
209                 && !conversionService.canBypassConvert(source, target);
210     }
211
212     /**
213      * Unwraps the given value if it's a JDK 8 {@link Optional}.
214      *
215      * @param source can be {@literal null}.
216      * @return
217      */

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     /**
232      * Returns whether we have to process the given source object in the first place.
233      *
234      * @param source can be {@literal null}.
235      * @param targetType must not be {@literal null}.
236      * @return
237      */

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     /**
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 nullif 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     }
294 }
295