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 java.util.List;
19 import java.util.Optional;
20
21 import javax.annotation.Nonnull;
22
23 import org.springframework.beans.BeansException;
24 import org.springframework.beans.factory.BeanClassLoaderAware;
25 import org.springframework.beans.factory.BeanFactory;
26 import org.springframework.beans.factory.BeanFactoryAware;
27 import org.springframework.beans.factory.FactoryBean;
28 import org.springframework.beans.factory.InitializingBean;
29 import org.springframework.beans.factory.ListableBeanFactory;
30 import org.springframework.context.ApplicationEventPublisher;
31 import org.springframework.context.ApplicationEventPublisherAware;
32 import org.springframework.data.mapping.PersistentEntity;
33 import org.springframework.data.mapping.context.MappingContext;
34 import org.springframework.data.repository.Repository;
35 import org.springframework.data.repository.core.EntityInformation;
36 import org.springframework.data.repository.core.NamedQueries;
37 import org.springframework.data.repository.core.RepositoryInformation;
38 import org.springframework.data.repository.core.RepositoryMetadata;
39 import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
40 import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
41 import org.springframework.data.repository.query.QueryLookupStrategy;
42 import org.springframework.data.repository.query.QueryLookupStrategy.Key;
43 import org.springframework.data.repository.query.QueryMethod;
44 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
45 import org.springframework.data.util.Lazy;
46 import org.springframework.util.Assert;
47
48 /**
49  * Adapter for Springs {@link FactoryBean} interface to allow easy setup of repository factories via Spring
50  * configuration.
51  *
52  * @param <T> the type of the repository
53  * @author Oliver Gierke
54  * @author Thomas Darimont
55  * @author Mark Paluch
56  */

57 public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID>
58         implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware,
59         BeanFactoryAware, ApplicationEventPublisherAware {
60
61     private final Class<? extends T> repositoryInterface;
62
63     private RepositoryFactorySupport factory;
64     private Key queryLookupStrategyKey;
65     private Optional<Class<?>> repositoryBaseClass = Optional.empty();
66     private Optional<Object> customImplementation = Optional.empty();
67     private Optional<RepositoryFragments> repositoryFragments = Optional.empty();
68     private NamedQueries namedQueries;
69     private Optional<MappingContext<?, ?>> mappingContext = Optional.empty();
70     private ClassLoader classLoader;
71     private BeanFactory beanFactory;
72     private boolean lazyInit = false;
73     private Optional<QueryMethodEvaluationContextProvider> evaluationContextProvider = Optional.empty();
74     private ApplicationEventPublisher publisher;
75
76     private Lazy<T> repository;
77
78     private RepositoryMetadata repositoryMetadata;
79
80     /**
81      * Creates a new {@link RepositoryFactoryBeanSupport} for the given repository interface.
82      *
83      * @param repositoryInterface must not be {@literal null}.
84      */

85     protected RepositoryFactoryBeanSupport(Class<? extends T> repositoryInterface) {
86
87         Assert.notNull(repositoryInterface, "Repository interface must not be null!");
88         this.repositoryInterface = repositoryInterface;
89     }
90
91     /**
92      * Configures the repository base class to be used.
93      *
94      * @param repositoryBaseClass the repositoryBaseClass to set, can be {@literal null}.
95      * @since 1.11
96      */

97     public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
98         this.repositoryBaseClass = Optional.ofNullable(repositoryBaseClass);
99     }
100
101     /**
102      * Set the {@link QueryLookupStrategy.Key} to be used.
103      *
104      * @param queryLookupStrategyKey
105      */

106     public void setQueryLookupStrategyKey(Key queryLookupStrategyKey) {
107         this.queryLookupStrategyKey = queryLookupStrategyKey;
108     }
109
110     /**
111      * Setter to inject a custom repository implementation.
112      *
113      * @param customImplementation
114      */

115     public void setCustomImplementation(Object customImplementation) {
116         this.customImplementation = Optional.of(customImplementation);
117     }
118
119     /**
120      * Setter to inject repository fragments.
121      *
122      * @param repositoryFragments
123      */

124     public void setRepositoryFragments(RepositoryFragments repositoryFragments) {
125         this.repositoryFragments = Optional.of(repositoryFragments);
126     }
127
128     /**
129      * Setter to inject a {@link NamedQueries} instance.
130      *
131      * @param namedQueries the namedQueries to set
132      */

133     public void setNamedQueries(NamedQueries namedQueries) {
134         this.namedQueries = namedQueries;
135     }
136
137     /**
138      * Configures the {@link MappingContext} to be used to lookup {@link PersistentEntity} instances for
139      * {@link #getPersistentEntity()}.
140      *
141      * @param mappingContext
142      */

143     protected void setMappingContext(MappingContext<?, ?> mappingContext) {
144         this.mappingContext = Optional.of(mappingContext);
145     }
146
147     /**
148      * Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined
149      * queries.
150      *
151      * @param evaluationContextProvider must not be {@literal null}.
152      */

153     public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
154         this.evaluationContextProvider = Optional.of(evaluationContextProvider);
155     }
156
157     /**
158      * Configures whether to initialize the repository proxy lazily. This defaults to {@literal false}.
159      *
160      * @param lazy whether to initialize the repository proxy lazily. This defaults to {@literal false}.
161      */

162     public void setLazyInit(boolean lazy) {
163         this.lazyInit = lazy;
164     }
165
166     /*
167      * (non-Javadoc)
168      * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
169      */

170     @Override
171     public void setBeanClassLoader(ClassLoader classLoader) {
172         this.classLoader = classLoader;
173     }
174
175     /*
176      * (non-Javadoc)
177      * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
178      */

179     @Override
180     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
181
182         this.beanFactory = beanFactory;
183
184         if (!this.evaluationContextProvider.isPresent() && ListableBeanFactory.class.isInstance(beanFactory)) {
185             this.evaluationContextProvider = Optional
186                     .of(new ExtensionAwareQueryMethodEvaluationContextProvider((ListableBeanFactory) beanFactory));
187         }
188     }
189
190     /*
191      * (non-Javadoc)
192      * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
193      */

194     @Override
195     public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
196         this.publisher = publisher;
197     }
198
199     /*
200      * (non-Javadoc)
201      * @see org.springframework.data.repository.core.support.RepositoryFactoryInformation#getEntityInformation()
202      */

203     @SuppressWarnings("unchecked")
204     public EntityInformation<S, ID> getEntityInformation() {
205         return (EntityInformation<S, ID>) factory.getEntityInformation(repositoryMetadata.getDomainType());
206     }
207
208     /*
209      * (non-Javadoc)
210      * @see org.springframework.data.repository.core.support.RepositoryFactoryInformation#getRepositoryInformation()
211      */

212     public RepositoryInformation getRepositoryInformation() {
213
214         RepositoryFragments fragments = customImplementation.map(RepositoryFragments::just)//
215                 .orElse(RepositoryFragments.empty());
216
217         return factory.getRepositoryInformation(repositoryMetadata, fragments);
218     }
219
220     /*
221      * (non-Javadoc)
222      * @see org.springframework.data.repository.core.support.RepositoryFactoryInformation#getPersistentEntity()
223      */

224     public PersistentEntity<?, ?> getPersistentEntity() {
225
226         return mappingContext.orElseThrow(() -> new IllegalStateException("No MappingContext available!"))
227                 .getRequiredPersistentEntity(repositoryMetadata.getDomainType());
228     }
229
230     /*
231      * (non-Javadoc)
232      * @see org.springframework.data.repository.core.support.RepositoryFactoryInformation#getQueryMethods()
233      */

234     public List<QueryMethod> getQueryMethods() {
235         return factory.getQueryMethods();
236     }
237
238     /*
239      * (non-Javadoc)
240      * @see org.springframework.beans.factory.FactoryBean#getObject()
241      */

242     @Nonnull
243     public T getObject() {
244         return this.repository.get();
245     }
246
247     /*
248      * (non-Javadoc)
249      * @see org.springframework.beans.factory.FactoryBean#getObjectType()
250      */

251     @Nonnull
252     public Class<? extends T> getObjectType() {
253         return repositoryInterface;
254     }
255
256     /*
257      * (non-Javadoc)
258      * @see org.springframework.beans.factory.FactoryBean#isSingleton()
259      */

260     public boolean isSingleton() {
261         return true;
262     }
263
264     /*
265      * (non-Javadoc)
266      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
267      */

268     public void afterPropertiesSet() {
269
270         this.factory = createRepositoryFactory();
271         this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
272         this.factory.setNamedQueries(namedQueries);
273         this.factory.setEvaluationContextProvider(
274                 evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));
275         this.factory.setBeanClassLoader(classLoader);
276         this.factory.setBeanFactory(beanFactory);
277
278         if (publisher != null) {
279             this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));
280         }
281
282         repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);
283
284         RepositoryFragments customImplementationFragment = customImplementation //
285                 .map(RepositoryFragments::just) //
286                 .orElseGet(RepositoryFragments::empty);
287
288         RepositoryFragments repositoryFragmentsToUse = this.repositoryFragments //
289                 .orElseGet(RepositoryFragments::empty) //
290                 .append(customImplementationFragment);
291
292         this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);
293
294         // Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)
295         this.mappingContext.ifPresent(it -> it.getPersistentEntity(repositoryMetadata.getDomainType()));
296
297         this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
298
299         if (!lazyInit) {
300             this.repository.get();
301         }
302     }
303
304     /**
305      * Create the actual {@link RepositoryFactorySupport} instance.
306      *
307      * @return
308      */

309     protected abstract RepositoryFactorySupport createRepositoryFactory();
310 }
311