1
16 package org.springframework.data.repository.query;
17
18 import lombok.AccessLevel;
19 import lombok.AllArgsConstructor;
20 import lombok.NonNull;
21 import lombok.RequiredArgsConstructor;
22
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.stream.Stream;
29
30 import javax.annotation.Nonnull;
31
32 import org.springframework.core.CollectionFactory;
33 import org.springframework.core.convert.ConversionService;
34 import org.springframework.core.convert.converter.Converter;
35 import org.springframework.core.convert.support.DefaultConversionService;
36 import org.springframework.data.domain.Slice;
37 import org.springframework.data.projection.ProjectionFactory;
38 import org.springframework.data.repository.util.ReactiveWrapperConverters;
39 import org.springframework.lang.Nullable;
40 import org.springframework.util.Assert;
41
42
52 @AllArgsConstructor(access = AccessLevel.PRIVATE)
53 public class ResultProcessor {
54
55 private final QueryMethod method;
56 private final ProjectingConverter converter;
57 private final ProjectionFactory factory;
58 private final ReturnedType type;
59
60
66 ResultProcessor(QueryMethod method, ProjectionFactory factory) {
67 this(method, factory, method.getReturnedObjectType());
68 }
69
70
77 private ResultProcessor(QueryMethod method, ProjectionFactory factory, Class<?> type) {
78
79 Assert.notNull(method, "QueryMethod must not be null!");
80 Assert.notNull(factory, "ProjectionFactory must not be null!");
81 Assert.notNull(type, "Type must not be null!");
82
83 this.method = method;
84 this.type = ReturnedType.of(type, method.getDomainClass(), factory);
85 this.converter = new ProjectingConverter(this.type, factory);
86 this.factory = factory;
87 }
88
89
95 public ResultProcessor withDynamicProjection(ParameterAccessor accessor) {
96
97 Assert.notNull(accessor, "Parameter accessor must not be null!");
98
99 Class<?> projection = accessor.findDynamicProjection();
100
101 return projection == null
102 ? this
103 : withType(projection);
104 }
105
106
111 public ReturnedType getReturnedType() {
112 return type;
113 }
114
115
121 @Nullable
122 public <T> T processResult(@Nullable Object source) {
123 return processResult(source, NoOpConverter.INSTANCE);
124 }
125
126
134 @Nullable
135 @SuppressWarnings("unchecked")
136 public <T> T processResult(@Nullable Object source, Converter<Object, Object> preparingConverter) {
137
138 if (source == null || type.isInstance(source) || !type.isProjecting()) {
139 return (T) source;
140 }
141
142 Assert.notNull(preparingConverter, "Preparing converter must not be null!");
143
144 ChainingConverter converter = ChainingConverter.of(type.getReturnedType(), preparingConverter).and(this.converter);
145
146 if (source instanceof Slice && method.isPageQuery() || method.isSliceQuery()) {
147 return (T) ((Slice<?>) source).map(converter::convert);
148 }
149
150 if (source instanceof Collection && method.isCollectionQuery()) {
151
152 Collection<?> collection = (Collection<?>) source;
153 Collection<Object> target = createCollectionFor(collection);
154
155 for (Object columns : collection) {
156 target.add(type.isInstance(columns) ? columns : converter.convert(columns));
157 }
158
159 return (T) target;
160 }
161
162 if (source instanceof Stream && method.isStreamQuery()) {
163 return (T) ((Stream<Object>) source).map(t -> type.isInstance(t) ? t : converter.convert(t));
164 }
165
166 if (ReactiveWrapperConverters.supports(source.getClass())) {
167 return (T) ReactiveWrapperConverters.map(source, converter::convert);
168 }
169
170 return (T) converter.convert(source);
171 }
172
173 private ResultProcessor withType(Class<?> type) {
174
175 ReturnedType returnedType = ReturnedType.of(type, method.getDomainClass(), factory);
176 return new ResultProcessor(method, converter.withType(returnedType), factory, returnedType);
177 }
178
179
186 private static Collection<Object> createCollectionFor(Collection<?> source) {
187
188 try {
189 return CollectionFactory.createCollection(source.getClass(), source.size());
190 } catch (RuntimeException o_O) {
191 return CollectionFactory.createApproximateCollection(source, source.size());
192 }
193 }
194
195 @RequiredArgsConstructor(staticName = "of")
196 private static class ChainingConverter implements Converter<Object, Object> {
197
198 private final @NonNull Class<?> targetType;
199 private final @NonNull Converter<Object, Object> delegate;
200
201
208 public ChainingConverter and(final Converter<Object, Object> converter) {
209
210 Assert.notNull(converter, "Converter must not be null!");
211
212 return new ChainingConverter(targetType, source -> {
213
214 Object intermediate = ChainingConverter.this.convert(source);
215
216 return intermediate == null || targetType.isInstance(intermediate) ? intermediate
217 : converter.convert(intermediate);
218 });
219 }
220
221
225 @Nullable
226 @Override
227 public Object convert(Object source) {
228 return delegate.convert(source);
229 }
230 }
231
232
238 private static enum NoOpConverter implements Converter<Object, Object> {
239
240 INSTANCE;
241
242
246 @Nonnull
247 @Override
248 public Object convert(Object source) {
249 return source;
250 }
251 }
252
253 @RequiredArgsConstructor
254 private static class ProjectingConverter implements Converter<Object, Object> {
255
256 private final @NonNull ReturnedType type;
257 private final @NonNull ProjectionFactory factory;
258 private final @NonNull ConversionService conversionService;
259
260
266 ProjectingConverter(ReturnedType type, ProjectionFactory factory) {
267 this(type, factory, DefaultConversionService.getSharedInstance());
268 }
269
270
276 ProjectingConverter withType(ReturnedType type) {
277
278 Assert.notNull(type, "ReturnedType must not be null!");
279
280 return new ProjectingConverter(type, factory, conversionService);
281 }
282
283
287 @Nullable
288 @Override
289 public Object convert(Object source) {
290
291 Class<?> targetType = type.getReturnedType();
292
293 if (targetType.isInterface()) {
294 return factory.createProjection(targetType, getProjectionTarget(source));
295 }
296
297 return conversionService.convert(source, targetType);
298 }
299
300 private Object getProjectionTarget(Object source) {
301
302 if (source != null && source.getClass().isArray()) {
303 source = Arrays.asList((Object[]) source);
304 }
305
306 if (source instanceof Collection) {
307 return toMap((Collection<?>) source, type.getInputProperties());
308 }
309
310 return source;
311 }
312
313 private static Map<String, Object> toMap(Collection<?> values, List<String> names) {
314
315 int i = 0;
316 Map<String, Object> result = new HashMap<>(values.size());
317
318 for (Object element : values) {
319 result.put(names.get(i++), element);
320 }
321
322 return result;
323 }
324 }
325 }
326