1 /*
2  * Copyright 2012-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.support;
17
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26
27 import org.springframework.beans.factory.BeanFactory;
28 import org.springframework.beans.factory.BeanFactoryUtils;
29 import org.springframework.beans.factory.ListableBeanFactory;
30 import org.springframework.beans.factory.config.BeanDefinition;
31 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
32 import org.springframework.data.mapping.PersistentEntity;
33 import org.springframework.data.mapping.context.MappingContext;
34 import org.springframework.data.repository.core.EntityInformation;
35 import org.springframework.data.repository.core.RepositoryInformation;
36 import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
37 import org.springframework.data.repository.query.QueryMethod;
38 import org.springframework.data.util.ProxyUtils;
39 import org.springframework.util.Assert;
40 import org.springframework.util.ClassUtils;
41
42 /**
43  * Wrapper class to access repository instances obtained from a {@link ListableBeanFactory}.
44  *
45  * @author Oliver Gierke
46  * @author Thomas Darimont
47  * @author Thomas Eizinger
48  * @author Christoph Strobl
49  */

50 public class Repositories implements Iterable<Class<?>> {
51
52     static final Repositories NONE = new Repositories();
53
54     private static final RepositoryFactoryInformation<Object, Object> EMPTY_REPOSITORY_FACTORY_INFO = EmptyRepositoryFactoryInformation.INSTANCE;
55     private static final String DOMAIN_TYPE_MUST_NOT_BE_NULL = "Domain type must not be null!";
56
57     private final Optional<BeanFactory> beanFactory;
58     private final Map<Class<?>, String> repositoryBeanNames;
59     private final Map<Class<?>, RepositoryFactoryInformation<Object, Object>> repositoryFactoryInfos;
60
61     /**
62      * Constructor to create the {@link #NONE} instance.
63      */

64     private Repositories() {
65
66         this.beanFactory = Optional.empty();
67         this.repositoryBeanNames = Collections.emptyMap();
68         this.repositoryFactoryInfos = Collections.emptyMap();
69     }
70
71     /**
72      * Creates a new {@link Repositories} instance by looking up the repository instances and meta information from the
73      * given {@link ListableBeanFactory}.
74      *
75      * @param factory must not be {@literal null}.
76      */

77     public Repositories(ListableBeanFactory factory) {
78
79         Assert.notNull(factory, "ListableBeanFactory must not be null!");
80
81         this.beanFactory = Optional.of(factory);
82         this.repositoryFactoryInfos = new HashMap<>();
83         this.repositoryBeanNames = new HashMap<>();
84
85         populateRepositoryFactoryInformation(factory);
86     }
87
88     private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
89
90         for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
91                 falsefalse)) {
92             cacheRepositoryFactory(name);
93         }
94     }
95
96     @SuppressWarnings("rawtypes")
97     private synchronized void cacheRepositoryFactory(String name) {
98
99         RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.get().getBean(name,
100                 RepositoryFactoryInformation.class);
101         Class<?> domainType = ClassUtils
102                 .getUserClass(repositoryFactoryInformation.getRepositoryInformation().getDomainType());
103
104         RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation();
105         Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
106
107         Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1);
108         typesToRegister.add(domainType);
109         typesToRegister.addAll(alternativeDomainTypes);
110
111         for (Class<?> type : typesToRegister) {
112             cacheFirstOrPrimary(type, repositoryFactoryInformation, BeanFactoryUtils.transformedBeanName(name));
113         }
114     }
115
116     /**
117      * Returns whether we have a repository instance registered to manage instances of the given domain class.
118      *
119      * @param domainClass must not be {@literal null}.
120      * @return
121      */

122     public boolean hasRepositoryFor(Class<?> domainClass) {
123
124         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
125
126         Class<?> userClass = ProxyUtils.getUserClass(domainClass);
127
128         return repositoryFactoryInfos.containsKey(userClass);
129     }
130
131     /**
132      * Returns the repository managing the given domain class.
133      *
134      * @param domainClass must not be {@literal null}.
135      * @return
136      */

137     public Optional<Object> getRepositoryFor(Class<?> domainClass) {
138
139         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
140
141         Class<?> userClass = ProxyUtils.getUserClass(domainClass);
142         Optional<String> repositoryBeanName = Optional.ofNullable(repositoryBeanNames.get(userClass));
143
144         return beanFactory.flatMap(it -> repositoryBeanName.map(it::getBean));
145     }
146
147     /**
148      * Returns the {@link RepositoryFactoryInformation} for the given domain class. The given <code>code</code> is
149      * converted to the actual user class if necessary, @see ProxyUtils#getUserClass.
150      *
151      * @param domainClass must not be {@literal null}.
152      * @return the {@link RepositoryFactoryInformation} for the given domain class or {@literal nullif no repository
153      *         registered for this domain class.
154      */

155     private RepositoryFactoryInformation<Object, Object> getRepositoryFactoryInfoFor(Class<?> domainClass) {
156
157         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
158
159         Class<?> userType = ProxyUtils.getUserClass(domainClass);
160         RepositoryFactoryInformation<Object, Object> repositoryInfo = repositoryFactoryInfos.get(userType);
161
162         if (repositoryInfo != null) {
163             return repositoryInfo;
164         }
165
166         if (!userType.equals(Object.class)) {
167             return getRepositoryFactoryInfoFor(userType.getSuperclass());
168         }
169
170         return EMPTY_REPOSITORY_FACTORY_INFO;
171     }
172
173     /**
174      * Returns the {@link EntityInformation} for the given domain class.
175      *
176      * @param domainClass must not be {@literal null}.
177      * @return
178      */

179     @SuppressWarnings("unchecked")
180     public <T, S> EntityInformation<T, S> getEntityInformationFor(Class<?> domainClass) {
181
182         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
183
184         return (EntityInformation<T, S>) getRepositoryFactoryInfoFor(domainClass).getEntityInformation();
185     }
186
187     /**
188      * Returns the {@link RepositoryInformation} for the given domain class.
189      *
190      * @param domainClass must not be {@literal null}.
191      * @return the {@link RepositoryInformation} for the given domain class or {@literal Optional#empty()} if no
192      *         repository registered for this domain class.
193      */

194     public Optional<RepositoryInformation> getRepositoryInformationFor(Class<?> domainClass) {
195
196         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
197
198         RepositoryFactoryInformation<Object, Object> information = getRepositoryFactoryInfoFor(domainClass);
199         return information == EMPTY_REPOSITORY_FACTORY_INFO ? Optional.empty()
200                 : Optional.of(information.getRepositoryInformation());
201     }
202
203     /**
204      * Returns the {@link RepositoryInformation} for the given domain type.
205      *
206      * @param domainType must not be {@literal null}.
207      * @return the {@link RepositoryInformation} for the given domain type.
208      * @throws IllegalArgumentException in case no {@link RepositoryInformation} could be found for the given domain type.
209      */

210     public RepositoryInformation getRequiredRepositoryInformation(Class<?> domainType) {
211
212         return getRepositoryInformationFor(domainType).orElseThrow(() -> new IllegalArgumentException(
213                 "No required RepositoryInformation found for domain type " + domainType.getName() + "!"));
214     }
215
216     /**
217      * Returns the {@link RepositoryInformation} for the given repository interface.
218      *
219      * @param repositoryInterface must not be {@literal null}.
220      * @return the {@link RepositoryInformation} for the given repository interface or {@literal null} there's no
221      *         repository instance registered for the given interface.
222      * @since 1.12
223      */

224     public Optional<RepositoryInformation> getRepositoryInformation(Class<?> repositoryInterface) {
225
226         return repositoryFactoryInfos.values().stream()//
227                 .map(RepositoryFactoryInformation::getRepositoryInformation)//
228                 .filter(information -> information.getRepositoryInterface().equals(repositoryInterface))//
229                 .findFirst();
230     }
231
232     /**
233      * Returns the {@link PersistentEntity} for the given domain class. Might return {@literal null} in case the module
234      * storing the given domain class does not support the mapping subsystem.
235      *
236      * @param domainClass must not be {@literal null}.
237      * @return the {@link PersistentEntity} for the given domain class or {@literal nullif no repository is registered
238      *         for the domain class or the repository is not backed by a {@link MappingContext} implementation.
239      */

240     public PersistentEntity<?, ?> getPersistentEntity(Class<?> domainClass) {
241
242         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
243         return getRepositoryFactoryInfoFor(domainClass).getPersistentEntity();
244     }
245
246     /**
247      * Returns the {@link QueryMethod}s contained in the repository managing the given domain class.
248      *
249      * @param domainClass must not be {@literal null}.
250      * @return
251      */

252     public List<QueryMethod> getQueryMethodsFor(Class<?> domainClass) {
253
254         Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
255         return getRepositoryFactoryInfoFor(domainClass).getQueryMethods();
256     }
257
258     /*
259      * (non-Javadoc)
260      * @see java.lang.Iterable#iterator()
261      */

262     public Iterator<Class<?>> iterator() {
263         return repositoryFactoryInfos.keySet().iterator();
264     }
265
266     /**
267      * Caches the repository information for the given domain type or overrides existing information in case the bean name
268      * points to a primary bean definition.
269      * 
270      * @param type must not be {@literal null}.
271      * @param information must not be {@literal null}.
272      * @param name must not be {@literal null}.
273      */

274     @SuppressWarnings({ "rawtypes""unchecked" })
275     private void cacheFirstOrPrimary(Class<?> type, RepositoryFactoryInformation information, String name) {
276
277         if (repositoryBeanNames.containsKey(type)) {
278
279             Boolean presentAndPrimary = beanFactory //
280                     .filter(ConfigurableListableBeanFactory.class::isInstance) //
281                     .map(ConfigurableListableBeanFactory.class::cast) //
282                     .map(it -> it.getBeanDefinition(name)) //
283                     .map(BeanDefinition::isPrimary) //
284                     .orElse(false);
285
286             if (!presentAndPrimary) {
287                 return;
288             }
289         }
290
291         this.repositoryFactoryInfos.put(type, information);
292         this.repositoryBeanNames.put(type, name);
293     }
294
295     /**
296      * Null-object to avoid nasty {@literal null} checks in cache lookups.
297      *
298      * @author Thomas Darimont
299      */

300     private static enum EmptyRepositoryFactoryInformation implements RepositoryFactoryInformation<Object, Object> {
301
302         INSTANCE;
303
304         @Override
305         public EntityInformation<Object, Object> getEntityInformation() {
306             throw new UnsupportedOperationException();
307         }
308
309         @Override
310         public RepositoryInformation getRepositoryInformation() {
311             throw new UnsupportedOperationException();
312         }
313
314         @Override
315         public PersistentEntity<?, ?> getPersistentEntity() {
316             throw new UnsupportedOperationException();
317         }
318
319         @Override
320         public List<QueryMethod> getQueryMethods() {
321             return Collections.emptyList();
322         }
323     }
324 }
325