1
16 package org.springframework.data.jpa.repository.support;
17
18 import java.lang.reflect.Method;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27 import java.util.function.Predicate;
28
29 import javax.persistence.LockModeType;
30 import javax.persistence.QueryHint;
31
32 import org.aopalliance.intercept.MethodInterceptor;
33 import org.aopalliance.intercept.MethodInvocation;
34
35 import org.springframework.aop.TargetSource;
36 import org.springframework.aop.framework.ProxyFactory;
37 import org.springframework.beans.factory.BeanClassLoaderAware;
38 import org.springframework.core.NamedThreadLocal;
39 import org.springframework.core.annotation.AnnotatedElementUtils;
40 import org.springframework.core.annotation.AnnotationUtils;
41 import org.springframework.data.jpa.repository.EntityGraph;
42 import org.springframework.data.jpa.repository.Lock;
43 import org.springframework.data.jpa.repository.QueryHints;
44 import org.springframework.data.repository.core.RepositoryInformation;
45 import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
46 import org.springframework.lang.Nullable;
47 import org.springframework.transaction.support.TransactionSynchronizationManager;
48 import org.springframework.util.Assert;
49 import org.springframework.util.ClassUtils;
50 import org.springframework.util.ReflectionUtils;
51
52
63 class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware {
64
65 private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
66
67
71 @Override
72 public void setBeanClassLoader(ClassLoader classLoader) {
73 this.classLoader = classLoader;
74 }
75
76
80 @Override
81 public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
82 factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation));
83 }
84
85
89 CrudMethodMetadata getCrudMethodMetadata() {
90
91 ProxyFactory factory = new ProxyFactory();
92
93 factory.addInterface(CrudMethodMetadata.class);
94 factory.setTargetSource(new ThreadBoundTargetSource());
95
96 return (CrudMethodMetadata) factory.getProxy(this.classLoader);
97 }
98
99
107 static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor {
108
109 private static final ThreadLocal<MethodInvocation> currentInvocation = new NamedThreadLocal<>(
110 "Current AOP method invocation");
111
112 private final ConcurrentMap<Method, CrudMethodMetadata> metadataCache = new ConcurrentHashMap<>();
113 private final Set<Method> implementations = new HashSet<>();
114
115 CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) {
116
117 ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add,
118 method -> !repositoryInformation.isQueryMethod(method));
119 }
120
121
128 static MethodInvocation currentInvocation() throws IllegalStateException {
129
130 MethodInvocation mi = currentInvocation.get();
131
132 if (mi == null)
133 throw new IllegalStateException(
134 "No MethodInvocation found: Check that an AOP invocation is in progress, and that the "
135 + "CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain.");
136 return mi;
137 }
138
139
143 @Override
144 public Object invoke(MethodInvocation invocation) throws Throwable {
145
146 Method method = invocation.getMethod();
147
148 if (!implementations.contains(method)) {
149 return invocation.proceed();
150 }
151
152 MethodInvocation oldInvocation = currentInvocation.get();
153 currentInvocation.set(invocation);
154
155 try {
156
157 CrudMethodMetadata metadata = (CrudMethodMetadata) TransactionSynchronizationManager.getResource(method);
158
159 if (metadata != null) {
160 return invocation.proceed();
161 }
162
163 CrudMethodMetadata methodMetadata = metadataCache.get(method);
164
165 if (methodMetadata == null) {
166
167 methodMetadata = new DefaultCrudMethodMetadata(method);
168 CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata);
169
170 if (tmp != null) {
171 methodMetadata = tmp;
172 }
173 }
174
175 TransactionSynchronizationManager.bindResource(method, methodMetadata);
176
177 try {
178 return invocation.proceed();
179 } finally {
180 TransactionSynchronizationManager.unbindResource(method);
181 }
182 } finally {
183 currentInvocation.set(oldInvocation);
184 }
185 }
186 }
187
188
194 private static class DefaultCrudMethodMetadata implements CrudMethodMetadata {
195
196 private final @Nullable LockModeType lockModeType;
197 private final Map<String, Object> queryHints;
198 private final Map<String, Object> getQueryHintsForCount;
199 private final Optional<EntityGraph> entityGraph;
200 private final Method method;
201
202
207 DefaultCrudMethodMetadata(Method method) {
208
209 Assert.notNull(method, "Method must not be null!");
210
211 this.lockModeType = findLockModeType(method);
212 this.queryHints = findQueryHints(method, it -> true);
213 this.getQueryHintsForCount = findQueryHints(method, QueryHints::forCounting);
214 this.entityGraph = findEntityGraph(method);
215 this.method = method;
216 }
217
218 private static Optional<EntityGraph> findEntityGraph(Method method) {
219 return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class));
220 }
221
222 @Nullable
223 private static LockModeType findLockModeType(Method method) {
224
225 Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class);
226 return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation);
227 }
228
229 private static Map<String, Object> findQueryHints(Method method, Predicate<QueryHints> annotationFilter) {
230
231 Map<String, Object> queryHints = new HashMap<>();
232 QueryHints queryHintsAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class);
233
234 if (queryHintsAnnotation != null && annotationFilter.test(queryHintsAnnotation)) {
235
236 for (QueryHint hint : queryHintsAnnotation.value()) {
237 queryHints.put(hint.name(), hint.value());
238 }
239 }
240
241 QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class);
242
243 if (queryHintAnnotation != null) {
244 queryHints.put(queryHintAnnotation.name(), queryHintAnnotation.value());
245 }
246
247 return Collections.unmodifiableMap(queryHints);
248 }
249
250
254 @Nullable
255 @Override
256 public LockModeType getLockModeType() {
257 return lockModeType;
258 }
259
260
264 @Override
265 public Map<String, Object> getQueryHints() {
266 return queryHints;
267 }
268
269
273 @Override
274 public Map<String, Object> getQueryHintsForCount() {
275 return getQueryHintsForCount;
276 }
277
278
282 @Override
283 public Optional<EntityGraph> getEntityGraph() {
284 return entityGraph;
285 }
286
287
291 @Override
292 public Method getMethod() {
293 return method;
294 }
295 }
296
297 private static class ThreadBoundTargetSource implements TargetSource {
298
299
303 @Override
304 public Class<?> getTargetClass() {
305 return CrudMethodMetadata.class;
306 }
307
308
312 @Override
313 public boolean isStatic() {
314 return false;
315 }
316
317
321 @Override
322 public Object getTarget() {
323
324 MethodInvocation invocation = CrudMethodMetadataPopulatingMethodInterceptor.currentInvocation();
325 return TransactionSynchronizationManager.getResource(invocation.getMethod());
326 }
327
328
332 @Override
333 public void releaseTarget(Object target) {}
334 }
335 }
336