1 /*
2  * Copyright 2008-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

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 /**
73  * Factory bean to create instances of a given repository interface. Creates a proxy implementing the configured
74  * repository interface and apply an advice handing the control to the {@code QueryExecutorMethodInterceptor}. Query
75  * detection strategy can be configured by setting {@link QueryLookupStrategy.Key}.
76  *
77  * @author Oliver Gierke
78  * @author Mark Paluch
79  * @author Christoph Strobl
80  * @author Jens Schauder
81  * @author John Blum
82  */

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     /**
152      * Sets the strategy of how to lookup a query to execute finders.
153      *
154      * @param key
155      */

156     public void setQueryLookupStrategyKey(Key key) {
157         this.queryLookupStrategyKey = key;
158     }
159
160     /**
161      * Configures a {@link NamedQueries} instance to be handed to the {@link QueryLookupStrategy} for query creation.
162      *
163      * @param namedQueries the namedQueries to set
164      */

165     public void setNamedQueries(NamedQueries namedQueries) {
166         this.namedQueries = namedQueries == null ? PropertiesBasedNamedQueries.EMPTY : namedQueries;
167     }
168
169     /*
170      * (non-Javadoc)
171      * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
172      */

173     @Override
174     public void setBeanClassLoader(ClassLoader classLoader) {
175         this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader;
176     }
177
178     /*
179      * (non-Javadoc)
180      * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
181      */

182     @Override
183     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
184         this.beanFactory = beanFactory;
185     }
186
187     /**
188      * Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined
189      * queries.
190      *
191      * @param evaluationContextProvider can be {@literal null}, defaults to
192      *          {@link DefaultQueryMethodEvaluationContextProvider#INSTANCE}.
193      */

194     public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
195         this.evaluationContextProvider = evaluationContextProvider == null ? QueryMethodEvaluationContextProvider.DEFAULT
196                 : evaluationContextProvider;
197     }
198
199     /**
200      * Configures the repository base class to use when creating the repository proxy. If not set, the factory will use
201      * the type returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default.
202      *
203      * @param repositoryBaseClass the repository base class to back the repository proxy, can be {@literal null}.
204      * @since 1.11
205      */

206     public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
207         this.repositoryBaseClass = Optional.ofNullable(repositoryBaseClass);
208     }
209
210     /**
211      * Adds a {@link QueryCreationListener} to the factory to plug in functionality triggered right after creation of
212      * {@link RepositoryQuery} instances.
213      *
214      * @param listener
215      */

216     public void addQueryCreationListener(QueryCreationListener<?> listener) {
217
218         Assert.notNull(listener, "Listener must not be null!");
219         this.queryPostProcessors.add(listener);
220     }
221
222     /**
223      * Adds {@link RepositoryProxyPostProcessor}s to the factory to allow manipulation of the {@link ProxyFactory} before
224      * the proxy gets created. Note that the {@link QueryExecutorMethodInterceptor} will be added to the proxy
225      * <em>after</em> the {@link RepositoryProxyPostProcessor}s are considered.
226      *
227      * @param processor
228      */

229     public void addRepositoryProxyPostProcessor(RepositoryProxyPostProcessor processor) {
230
231         Assert.notNull(processor, "RepositoryProxyPostProcessor must not be null!");
232         this.postProcessors.add(processor);
233     }
234
235     /**
236      * Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add repository-specific extensions.
237      *
238      * @param metadata
239      * @return
240      */

241     protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
242         return RepositoryFragments.empty();
243     }
244
245     /**
246      * Creates {@link RepositoryComposition} based on {@link RepositoryMetadata} for repository-specific method handling.
247      *
248      * @param metadata
249      * @return
250      */

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     /**
264      * Returns a repository instance for the given interface.
265      *
266      * @param repositoryInterface must not be {@literal null}.
267      * @return
268      */

269     public <T> T getRepository(Class<T> repositoryInterface) {
270         return getRepository(repositoryInterface, RepositoryFragments.empty());
271     }
272
273     /**
274      * Returns a repository instance for the given interface backed by a single instance providing implementation logic
275      * for custom logic. For more advanced composition needs use {@link #getRepository(Class, RepositoryFragments)}.
276      *
277      * @param repositoryInterface must not be {@literal null}.
278      * @param customImplementation must not be {@literal null}.
279      * @return
280      */

281     public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
282         return getRepository(repositoryInterface, RepositoryFragments.just(customImplementation));
283     }
284
285     /**
286      * Returns a repository instance for the given interface backed by an instance providing implementation logic for
287      * custom logic.
288      *
289      * @param repositoryInterface must not be {@literal null}.
290      * @param fragments must not be {@literal null}.
291      * @return
292      * @since 2.0
293      */

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         // Create proxy
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     /**
348      * Returns the {@link ProjectionFactory} to be used with the repository instances created.
349      *
350      * @param classLoader will never be {@literal null}.
351      * @param beanFactory will never be {@literal null}.
352      * @return will never be {@literal null}.
353      */

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     /**
364      * Returns the {@link RepositoryMetadata} for the given repository interface.
365      *
366      * @param repositoryInterface will never be {@literal null}.
367      * @return
368      */

369     protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
370         return AbstractRepositoryMetadata.getMetadata(repositoryInterface);
371     }
372
373     /**
374      * Returns the {@link RepositoryInformation} for the given {@link RepositoryMetadata} and custom
375      * {@link RepositoryFragments}.
376      *
377      * @param metadata must not be {@literal null}.
378      * @param fragments must not be {@literal null}.
379      * @return will never be {@literal null}.
380      */

381     protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, RepositoryFragments fragments) {
382         return getRepositoryInformation(metadata, getRepositoryComposition(metadata, fragments));
383     }
384
385     /**
386      * Returns the {@link RepositoryComposition} for the given {@link RepositoryMetadata} and extra
387      * {@link RepositoryFragments}.
388      *
389      * @param metadata must not be {@literal null}.
390      * @param fragments must not be {@literal null}.
391      * @return will never be {@literal null}.
392      */

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     /**
405      * Returns the {@link RepositoryInformation} for the given repository interface.
406      *
407      * @param metadata
408      * @param composition
409      * @return
410      */

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     /**
429      * Returns the {@link EntityInformation} for the given domain class.
430      *
431      * @param <T> the entity type
432      * @param <ID> the id type
433      * @param domainClass
434      * @return
435      */

436     public abstract <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass);
437
438     /**
439      * Create a repository instance as backing for the query proxy.
440      *
441      * @param metadata
442      * @return
443      */

444     protected abstract Object getTargetRepository(RepositoryInformation metadata);
445
446     /**
447      * Returns the base class backing the actual repository instance. Make sure
448      * {@link #getTargetRepository(RepositoryInformation)} returns an instance of this class.
449      *
450      * @param metadata
451      * @return
452      */

453     protected abstract Class<?> getRepositoryBaseClass(RepositoryMetadata metadata);
454
455     /**
456      * Returns the {@link QueryLookupStrategy} for the given {@link Key} and {@link QueryMethodEvaluationContextProvider}.
457      *
458      * @param key can be {@literal null}.
459      * @param evaluationContextProvider will never be {@literal null}.
460      * @return the {@link QueryLookupStrategy} to use or {@literal nullif no queries should be looked up.
461      * @since 1.9
462      */

463     protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
464             QueryMethodEvaluationContextProvider evaluationContextProvider) {
465         return Optional.empty();
466     }
467
468     /**
469      * Validates the given repository interface as well as the given custom implementation.
470      *
471      * @param repositoryInformation
472      * @param composition
473      */

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     /**
496      * Creates a repository of the repository base class defined in the given {@link RepositoryInformation} using
497      * reflection.
498      *
499      * @param information
500      * @param constructorArguments
501      * @return
502      */

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     /**
511      * Creates a repository of the repository base class defined in the given {@link RepositoryInformation} using
512      * reflection.
513      *
514      * @param baseClass
515      * @param constructorArguments
516      * @return
517      */

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     /**
529      * Method interceptor that calls methods on the {@link RepositoryComposition}.
530      *
531      * @author Mark Paluch
532      */

533     @RequiredArgsConstructor
534     public class ImplementationMethodExecutionInterceptor implements MethodInterceptor {
535
536         private final @NonNull RepositoryComposition composition;
537
538         /*
539          * (non-Javadoc)
540          * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
541          */

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     /**
560      * {@link QueryCreationListener} collecting the {@link QueryMethod}s created for all query methods of the repository
561      * interface.
562      *
563      * @author Oliver Gierke
564      */

565     @Getter
566     private static class QueryCollectingQueryCreationListener implements QueryCreationListener<RepositoryQuery> {
567
568         /**
569          * All {@link QueryMethod}s.
570          */

571         private final List<QueryMethod> queryMethods = new ArrayList<>();
572
573         /* (non-Javadoc)
574          * @see org.springframework.data.repository.core.support.QueryCreationListener#onCreation(org.springframework.data.repository.query.RepositoryQuery)
575          */

576         @Override
577         public void onCreation(RepositoryQuery query) {
578             this.queryMethods.add(query.getQueryMethod());
579         }
580     }
581
582     /**
583      * Simple value object to build up keys to cache {@link RepositoryInformation} instances.
584      *
585      * @author Oliver Gierke
586      * @author Mark Paluch
587      */

588     @EqualsAndHashCode
589     @Value
590     private static class RepositoryInformationCacheKey {
591
592         String repositoryInterfaceName;
593         final long compositionHash;
594
595         /**
596          * Creates a new {@link RepositoryInformationCacheKey} for the given {@link RepositoryMetadata} and composition.
597          *
598          * @param metadata must not be {@literal null}.
599          * @param composition must not be {@literal null}.
600          */

601         public RepositoryInformationCacheKey(RepositoryMetadata metadata, RepositoryComposition composition) {
602
603             this.repositoryInterfaceName = metadata.getRepositoryInterface().getName();
604             this.compositionHash = composition.hashCode();
605         }
606     }
607 }
608