1
16 package org.springframework.data.repository.core.support;
17
18 import lombok.EqualsAndHashCode;
19 import lombok.Getter;
20 import lombok.NonNull;
21 import lombok.RequiredArgsConstructor;
22 import lombok.Value;
23 import lombok.extern.slf4j.Slf4j;
24
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.function.BiFunction;
33 import java.util.stream.Collectors;
34
35 import org.aopalliance.intercept.MethodInterceptor;
36 import org.aopalliance.intercept.MethodInvocation;
37
38 import org.springframework.aop.framework.ProxyFactory;
39 import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
40 import org.springframework.beans.BeanUtils;
41 import org.springframework.beans.BeansException;
42 import org.springframework.beans.factory.BeanClassLoaderAware;
43 import org.springframework.beans.factory.BeanFactory;
44 import org.springframework.beans.factory.BeanFactoryAware;
45 import org.springframework.core.convert.support.DefaultConversionService;
46 import org.springframework.core.convert.support.GenericConversionService;
47 import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
48 import org.springframework.data.projection.ProjectionFactory;
49 import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
50 import org.springframework.data.repository.Repository;
51 import org.springframework.data.repository.core.EntityInformation;
52 import org.springframework.data.repository.core.NamedQueries;
53 import org.springframework.data.repository.core.RepositoryInformation;
54 import org.springframework.data.repository.core.RepositoryMetadata;
55 import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
56 import org.springframework.data.repository.query.QueryLookupStrategy;
57 import org.springframework.data.repository.query.QueryLookupStrategy.Key;
58 import org.springframework.data.repository.query.QueryMethod;
59 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
60 import org.springframework.data.repository.query.RepositoryQuery;
61 import org.springframework.data.repository.util.ClassUtils;
62 import org.springframework.data.repository.util.QueryExecutionConverters;
63 import org.springframework.data.repository.util.ReactiveWrapperConverters;
64 import org.springframework.data.repository.util.ReactiveWrappers;
65 import org.springframework.data.util.ReflectionUtils;
66 import org.springframework.lang.Nullable;
67 import org.springframework.transaction.interceptor.TransactionalProxy;
68 import org.springframework.util.Assert;
69 import org.springframework.util.ConcurrentReferenceHashMap;
70 import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
71
72
83 @Slf4j
84 public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
85
86 private static final BiFunction<Method, Object[], Object[]> REACTIVE_ARGS_CONVERTER = (method, args) -> {
87
88 if (ReactiveWrappers.isAvailable()) {
89
90 Class<?>[] parameterTypes = method.getParameterTypes();
91
92 Object[] converted = new Object[args.length];
93 for (int i = 0; i < args.length; i++) {
94
95 Object value = args[i];
96 Object convertedArg = value;
97
98 Class<?> parameterType = parameterTypes.length > i ? parameterTypes[i] : null;
99
100 if (value != null && parameterType != null) {
101 if (!parameterType.isAssignableFrom(value.getClass())
102 && ReactiveWrapperConverters.canConvert(value.getClass(), parameterType)) {
103
104 convertedArg = ReactiveWrapperConverters.toWrapper(value, parameterType);
105 }
106 }
107
108 converted[i] = convertedArg;
109 }
110
111 return converted;
112 }
113
114 return args;
115 };
116
117 final static GenericConversionService CONVERSION_SERVICE = new DefaultConversionService();
118
119 static {
120 QueryExecutionConverters.registerConvertersIn(CONVERSION_SERVICE);
121 CONVERSION_SERVICE.removeConvertible(Object.class, Object.class);
122 }
123
124 private final Map<RepositoryInformationCacheKey, RepositoryInformation> repositoryInformationCache;
125 private final List<RepositoryProxyPostProcessor> postProcessors;
126
127 private Optional<Class<?>> repositoryBaseClass;
128 private @Nullable QueryLookupStrategy.Key queryLookupStrategyKey;
129 private List<QueryCreationListener<?>> queryPostProcessors;
130 private NamedQueries namedQueries;
131 private ClassLoader classLoader;
132 private QueryMethodEvaluationContextProvider evaluationContextProvider;
133 private BeanFactory beanFactory;
134
135 private final QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
136
137 @SuppressWarnings("null")
138 public RepositoryFactorySupport() {
139
140 this.repositoryInformationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
141 this.postProcessors = new ArrayList<>();
142
143 this.repositoryBaseClass = Optional.empty();
144 this.namedQueries = PropertiesBasedNamedQueries.EMPTY;
145 this.classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
146 this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT;
147 this.queryPostProcessors = new ArrayList<>();
148 this.queryPostProcessors.add(collectingListener);
149 }
150
151
156 public void setQueryLookupStrategyKey(Key key) {
157 this.queryLookupStrategyKey = key;
158 }
159
160
165 public void setNamedQueries(NamedQueries namedQueries) {
166 this.namedQueries = namedQueries == null ? PropertiesBasedNamedQueries.EMPTY : namedQueries;
167 }
168
169
173 @Override
174 public void setBeanClassLoader(ClassLoader classLoader) {
175 this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader;
176 }
177
178
182 @Override
183 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
184 this.beanFactory = beanFactory;
185 }
186
187
194 public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
195 this.evaluationContextProvider = evaluationContextProvider == null ? QueryMethodEvaluationContextProvider.DEFAULT
196 : evaluationContextProvider;
197 }
198
199
206 public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
207 this.repositoryBaseClass = Optional.ofNullable(repositoryBaseClass);
208 }
209
210
216 public void addQueryCreationListener(QueryCreationListener<?> listener) {
217
218 Assert.notNull(listener, "Listener must not be null!");
219 this.queryPostProcessors.add(listener);
220 }
221
222
229 public void addRepositoryProxyPostProcessor(RepositoryProxyPostProcessor processor) {
230
231 Assert.notNull(processor, "RepositoryProxyPostProcessor must not be null!");
232 this.postProcessors.add(processor);
233 }
234
235
241 protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
242 return RepositoryFragments.empty();
243 }
244
245
251 private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata) {
252
253 RepositoryComposition composition = RepositoryComposition.empty();
254
255 if (metadata.isReactiveRepository()) {
256 return composition.withMethodLookup(MethodLookups.forReactiveTypes(metadata))
257 .withArgumentConverter(REACTIVE_ARGS_CONVERTER);
258 }
259
260 return composition.withMethodLookup(MethodLookups.forRepositoryTypes(metadata));
261 }
262
263
269 public <T> T getRepository(Class<T> repositoryInterface) {
270 return getRepository(repositoryInterface, RepositoryFragments.empty());
271 }
272
273
281 public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
282 return getRepository(repositoryInterface, RepositoryFragments.just(customImplementation));
283 }
284
285
294 @SuppressWarnings({ "unchecked" })
295 public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
296
297 if (LOG.isDebugEnabled()) {
298 LOG.debug("Initializing repository instance for {}…", repositoryInterface.getName());
299 }
300
301 Assert.notNull(repositoryInterface, "Repository interface must not be null!");
302 Assert.notNull(fragments, "RepositoryFragments must not be null!");
303
304 RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
305 RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
306 RepositoryInformation information = getRepositoryInformation(metadata, composition);
307
308 validate(information, composition);
309
310 Object target = getTargetRepository(information);
311
312
313 ProxyFactory result = new ProxyFactory();
314 result.setTarget(target);
315 result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
316
317 if (MethodInvocationValidator.supports(repositoryInterface)) {
318 result.addAdvice(new MethodInvocationValidator());
319 }
320
321 result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
322
323 postProcessors.forEach(processor -> processor.postProcess(result, information));
324
325 if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
326 result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
327 }
328
329 ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
330 Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
331 evaluationContextProvider);
332 result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
333 namedQueries, queryPostProcessors));
334
335 composition = composition.append(RepositoryFragment.implemented(target));
336 result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));
337
338 T repository = (T) result.getProxy(classLoader);
339
340 if (LOG.isDebugEnabled()) {
341 LOG.debug("Finished creation of repository instance for {}.", repositoryInterface.getName());
342 }
343
344 return repository;
345 }
346
347
354 protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
355
356 SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
357 factory.setBeanClassLoader(classLoader);
358 factory.setBeanFactory(beanFactory);
359
360 return factory;
361 }
362
363
369 protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
370 return AbstractRepositoryMetadata.getMetadata(repositoryInterface);
371 }
372
373
381 protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, RepositoryFragments fragments) {
382 return getRepositoryInformation(metadata, getRepositoryComposition(metadata, fragments));
383 }
384
385
393 private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata, RepositoryFragments fragments) {
394
395 Assert.notNull(metadata, "RepositoryMetadata must not be null!");
396 Assert.notNull(fragments, "RepositoryFragments must not be null!");
397
398 RepositoryComposition composition = getRepositoryComposition(metadata);
399 RepositoryFragments repositoryAspects = getRepositoryFragments(metadata);
400
401 return composition.append(fragments).append(repositoryAspects);
402 }
403
404
411 private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,
412 RepositoryComposition composition) {
413
414 RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition);
415
416 return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {
417
418 Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));
419
420 return new DefaultRepositoryInformation(metadata, baseClass, composition);
421 });
422 }
423
424 protected List<QueryMethod> getQueryMethods() {
425 return collectingListener.getQueryMethods();
426 }
427
428
436 public abstract <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass);
437
438
444 protected abstract Object getTargetRepository(RepositoryInformation metadata);
445
446
453 protected abstract Class<?> getRepositoryBaseClass(RepositoryMetadata metadata);
454
455
463 protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
464 QueryMethodEvaluationContextProvider evaluationContextProvider) {
465 return Optional.empty();
466 }
467
468
474 private void validate(RepositoryInformation repositoryInformation, RepositoryComposition composition) {
475
476 if (repositoryInformation.hasCustomMethod()) {
477
478 if (composition.isEmpty()) {
479
480 throw new IllegalArgumentException(
481 String.format("You have custom methods in %s but have not provided a custom implementation!",
482 repositoryInformation.getRepositoryInterface()));
483 }
484
485 composition.validateImplementation();
486 }
487
488 validate(repositoryInformation);
489 }
490
491 protected void validate(RepositoryMetadata repositoryMetadata) {
492
493 }
494
495
503 protected final <R> R getTargetRepositoryViaReflection(RepositoryInformation information,
504 Object... constructorArguments) {
505
506 Class<?> baseClass = information.getRepositoryBaseClass();
507 return getTargetRepositoryViaReflection(baseClass, constructorArguments);
508 }
509
510
518 @SuppressWarnings("unchecked")
519 protected final <R> R getTargetRepositoryViaReflection(Class<?> baseClass, Object... constructorArguments) {
520 Optional<Constructor<?>> constructor = ReflectionUtils.findConstructor(baseClass, constructorArguments);
521
522 return constructor.map(it -> (R) BeanUtils.instantiateClass(it, constructorArguments))
523 .orElseThrow(() -> new IllegalStateException(String.format(
524 "No suitable constructor found on %s to match the given arguments: %s. Make sure you implement a constructor taking these",
525 baseClass, Arrays.stream(constructorArguments).map(Object::getClass).collect(Collectors.toList()))));
526 }
527
528
533 @RequiredArgsConstructor
534 public class ImplementationMethodExecutionInterceptor implements MethodInterceptor {
535
536 private final @NonNull RepositoryComposition composition;
537
538
542 @Nullable
543 @Override
544 public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
545
546 Method method = invocation.getMethod();
547 Object[] arguments = invocation.getArguments();
548
549 try {
550 return composition.invoke(method, arguments);
551 } catch (Exception e) {
552 ClassUtils.unwrapReflectionException(e);
553 }
554
555 throw new IllegalStateException("Should not occur!");
556 }
557 }
558
559
565 @Getter
566 private static class QueryCollectingQueryCreationListener implements QueryCreationListener<RepositoryQuery> {
567
568
571 private final List<QueryMethod> queryMethods = new ArrayList<>();
572
573
576 @Override
577 public void onCreation(RepositoryQuery query) {
578 this.queryMethods.add(query.getQueryMethod());
579 }
580 }
581
582
588 @EqualsAndHashCode
589 @Value
590 private static class RepositoryInformationCacheKey {
591
592 String repositoryInterfaceName;
593 final long compositionHash;
594
595
601 public RepositoryInformationCacheKey(RepositoryMetadata metadata, RepositoryComposition composition) {
602
603 this.repositoryInterfaceName = metadata.getRepositoryInterface().getName();
604 this.compositionHash = composition.hashCode();
605 }
606 }
607 }
608