1
16 package org.springframework.data.projection;
17
18 import java.lang.reflect.Method;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.aopalliance.intercept.MethodInterceptor;
25 import org.aopalliance.intercept.MethodInvocation;
26 import org.springframework.aop.framework.Advised;
27 import org.springframework.aop.framework.ProxyFactory;
28 import org.springframework.beans.factory.BeanClassLoaderAware;
29 import org.springframework.core.convert.ConversionService;
30 import org.springframework.core.convert.support.DefaultConversionService;
31 import org.springframework.lang.Nullable;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ClassUtils;
34 import org.springframework.util.ConcurrentReferenceHashMap;
35
36
48 class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware {
49
50 private final List<MethodInterceptorFactory> factories;
51 private final ConversionService conversionService;
52 private final Map<Class<?>, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>();
53 private @Nullable ClassLoader classLoader;
54
55
58 protected ProxyProjectionFactory() {
59
60 this.factories = new ArrayList<>();
61 this.factories.add(MapAccessingMethodInterceptorFactory.INSTANCE);
62 this.factories.add(PropertyAccessingMethodInvokerFactory.INSTANCE);
63
64 this.conversionService = DefaultConversionService.getSharedInstance();
65 }
66
67
71 @Override
72 public void setBeanClassLoader(ClassLoader classLoader) {
73 this.classLoader = classLoader;
74 }
75
76
83 public void registerMethodInvokerFactory(MethodInterceptorFactory factory) {
84
85 Assert.notNull(factory, "MethodInterceptorFactory must not be null!");
86
87 this.factories.add(0, factory);
88 }
89
90
94 @Override
95 @SuppressWarnings("unchecked")
96 public <T> T createProjection(Class<T> projectionType, Object source) {
97
98 Assert.notNull(projectionType, "Projection type must not be null!");
99 Assert.notNull(source, "Source must not be null!");
100 Assert.isTrue(projectionType.isInterface(), "Projection type must be an interface!");
101
102 if (projectionType.isInstance(source)) {
103 return (T) source;
104 }
105
106 ProxyFactory factory = new ProxyFactory();
107 factory.setTarget(source);
108 factory.setOpaque(true);
109 factory.setInterfaces(projectionType, TargetAware.class);
110
111 factory.addAdvice(new DefaultMethodInvokingMethodInterceptor());
112 factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
113 factory.addAdvice(getMethodInterceptor(source, projectionType));
114
115 return (T) factory.getProxy(classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader);
116 }
117
118
122 @Override
123 public <T> T createProjection(Class<T> projectionType) {
124
125 Assert.notNull(projectionType, "Projection type must not be null!");
126
127 return createProjection(projectionType, new HashMap<String, Object>());
128 }
129
130
134 @Override
135 public final ProjectionInformation getProjectionInformation(Class<?> projectionType) {
136
137 return projectionInformationCache.computeIfAbsent(projectionType, this::createProjectionInformation);
138 }
139
140
149 protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor interceptor, Object source,
150 Class<?> projectionType) {
151 return interceptor;
152 }
153
154
160 protected ProjectionInformation createProjectionInformation(Class<?> projectionType) {
161 return new DefaultProjectionInformation(projectionType);
162 }
163
164
171 private MethodInterceptor getMethodInterceptor(Object source, Class<?> projectionType) {
172
173 MethodInterceptor propertyInvocationInterceptor = getFactoryFor(source, projectionType)
174 .createMethodInterceptor(source, projectionType);
175
176 return new ProjectingMethodInterceptor(this,
177 postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType), conversionService);
178 }
179
180
187 private MethodInterceptorFactory getFactoryFor(Object source, Class<?> projectionType) {
188
189 for (MethodInterceptorFactory factory : factories) {
190 if (factory.supports(source, projectionType)) {
191 return factory;
192 }
193 }
194
195 throw new IllegalStateException("No MethodInterceptorFactory found for type ".concat(source.getClass().getName()));
196 }
197
198
204 private static class TargetAwareMethodInterceptor implements MethodInterceptor {
205
206 private static final Method GET_TARGET_CLASS_METHOD;
207 private static final Method GET_TARGET_METHOD;
208
209 private final Class<?> targetType;
210
211 static {
212 try {
213 GET_TARGET_CLASS_METHOD = TargetAware.class.getMethod("getTargetClass");
214 GET_TARGET_METHOD = TargetAware.class.getMethod("getTarget");
215 } catch (NoSuchMethodException e) {
216 throw new IllegalStateException(e);
217 }
218 }
219
220
225 public TargetAwareMethodInterceptor(Class<?> targetType) {
226
227 Assert.notNull(targetType, "Target type must not be null!");
228 this.targetType = targetType;
229 }
230
231
235 @Nullable
236 @Override
237 public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
238
239 if (invocation.getMethod().equals(GET_TARGET_CLASS_METHOD)) {
240 return targetType;
241 } else if (invocation.getMethod().equals(GET_TARGET_METHOD)) {
242 return invocation.getThis();
243 }
244
245 return invocation.proceed();
246 }
247 }
248
249
254 private static enum MapAccessingMethodInterceptorFactory implements MethodInterceptorFactory {
255
256 INSTANCE;
257
258
262 @Override
263 @SuppressWarnings("unchecked")
264 public MethodInterceptor createMethodInterceptor(Object source, Class<?> targetType) {
265 return new MapAccessingMethodInterceptor((Map<String, Object>) source);
266 }
267
268
272 @Override
273 public boolean supports(Object source, Class<?> targetType) {
274 return Map.class.isInstance(source);
275 }
276 }
277
278
283 private static enum PropertyAccessingMethodInvokerFactory implements MethodInterceptorFactory {
284
285 INSTANCE;
286
287
291 @Override
292 public MethodInterceptor createMethodInterceptor(Object source, Class<?> targetType) {
293 return new PropertyAccessingMethodInterceptor(source);
294 }
295
296
300 @Override
301 public boolean supports(Object source, Class<?> targetType) {
302 return true;
303 }
304 }
305 }
306