1
16 package org.springframework.data.projection;
17
18 import lombok.NonNull;
19 import lombok.RequiredArgsConstructor;
20
21 import java.lang.reflect.Array;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28
29 import javax.annotation.Nonnull;
30
31 import org.aopalliance.intercept.MethodInterceptor;
32 import org.aopalliance.intercept.MethodInvocation;
33 import org.springframework.core.CollectionFactory;
34 import org.springframework.core.convert.ConversionService;
35 import org.springframework.data.util.ClassTypeInformation;
36 import org.springframework.data.util.TypeInformation;
37 import org.springframework.lang.Nullable;
38 import org.springframework.util.Assert;
39 import org.springframework.util.ClassUtils;
40 import org.springframework.util.ObjectUtils;
41
42
50 @RequiredArgsConstructor
51 class ProjectingMethodInterceptor implements MethodInterceptor {
52
53 private final @NonNull ProjectionFactory factory;
54 private final @NonNull MethodInterceptor delegate;
55 private final @NonNull ConversionService conversionService;
56
57
61 @Nullable
62 @Override
63 public Object invoke(@SuppressWarnings("null") @Nonnull MethodInvocation invocation) throws Throwable {
64
65 Object result = delegate.invoke(invocation);
66
67 if (result == null) {
68 return null;
69 }
70
71 TypeInformation<?> type = ClassTypeInformation.fromReturnTypeOf(invocation.getMethod());
72 Class<?> rawType = type.getType();
73
74 if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(rawType)) {
75 return projectCollectionElements(asCollection(result), type);
76 } else if (type.isMap()) {
77 return projectMapValues((Map<?, ?>) result, type);
78 } else if (conversionRequiredAndPossible(result, rawType)) {
79 return conversionService.convert(result, rawType);
80 } else {
81 return getProjection(result, rawType);
82 }
83 }
84
85
93 private Object projectCollectionElements(Collection<?> sources, TypeInformation<?> type) {
94
95 Class<?> rawType = type.getType();
96 TypeInformation<?> componentType = type.getComponentType();
97 Collection<Object> result = CollectionFactory.createCollection(rawType.isArray() ? List.class : rawType,
98 componentType != null ? componentType.getType() : null, sources.size());
99
100 for (Object source : sources) {
101 result.add(getProjection(source, type.getRequiredComponentType().getType()));
102 }
103
104 if (rawType.isArray()) {
105 return result.toArray((Object[]) Array.newInstance(type.getRequiredComponentType().getType(), result.size()));
106 }
107
108 return result;
109 }
110
111
119 private Map<Object, Object> projectMapValues(Map<?, ?> sources, TypeInformation<?> type) {
120
121 Map<Object, Object> result = CollectionFactory.createMap(type.getType(), sources.size());
122
123 for (Entry<?, ?> source : sources.entrySet()) {
124 result.put(source.getKey(), getProjection(source.getValue(), type.getRequiredMapValueType().getType()));
125 }
126
127 return result;
128 }
129
130 @Nullable
131 private Object getProjection(Object result, Class<?> returnType) {
132 return result == null || ClassUtils.isAssignable(returnType, result.getClass()) ? result
133 : factory.createProjection(returnType, result);
134 }
135
136
144 private boolean conversionRequiredAndPossible(Object source, Class<?> targetType) {
145
146 if (source == null || targetType.isInstance(source)) {
147 return false;
148 }
149
150 return conversionService.canConvert(source.getClass(), targetType);
151 }
152
153
160 private static Collection<?> asCollection(Object source) {
161
162 Assert.notNull(source, "Source object must not be null!");
163
164 if (source instanceof Collection) {
165 return (Collection<?>) source;
166 } else if (source.getClass().isArray()) {
167 return Arrays.asList(ObjectUtils.toObjectArray(source));
168 } else {
169 return Collections.singleton(source);
170 }
171 }
172 }
173