/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ProxyUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
Wrapper class to access repository instances obtained from a ListableBeanFactory
. Author: Oliver Gierke, Thomas Darimont, Thomas Eizinger, Christoph Strobl
/**
* Wrapper class to access repository instances obtained from a {@link ListableBeanFactory}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Thomas Eizinger
* @author Christoph Strobl
*/
public class Repositories implements Iterable<Class<?>> {
static final Repositories NONE = new Repositories();
private static final RepositoryFactoryInformation<Object, Object> EMPTY_REPOSITORY_FACTORY_INFO = EmptyRepositoryFactoryInformation.INSTANCE;
private static final String DOMAIN_TYPE_MUST_NOT_BE_NULL = "Domain type must not be null!";
private final Optional<BeanFactory> beanFactory;
private final Map<Class<?>, String> repositoryBeanNames;
private final Map<Class<?>, RepositoryFactoryInformation<Object, Object>> repositoryFactoryInfos;
Constructor to create the NONE
instance. /**
* Constructor to create the {@link #NONE} instance.
*/
private Repositories() {
this.beanFactory = Optional.empty();
this.repositoryBeanNames = Collections.emptyMap();
this.repositoryFactoryInfos = Collections.emptyMap();
}
Creates a new Repositories
instance by looking up the repository instances and meta information from the given ListableBeanFactory
. Params: - factory – must not be null.
/**
* Creates a new {@link Repositories} instance by looking up the repository instances and meta information from the
* given {@link ListableBeanFactory}.
*
* @param factory must not be {@literal null}.
*/
public Repositories(ListableBeanFactory factory) {
Assert.notNull(factory, "ListableBeanFactory must not be null!");
this.beanFactory = Optional.of(factory);
this.repositoryFactoryInfos = new HashMap<>();
this.repositoryBeanNames = new HashMap<>();
populateRepositoryFactoryInformation(factory);
}
private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
false, false)) {
cacheRepositoryFactory(name);
}
}
@SuppressWarnings("rawtypes")
private synchronized void cacheRepositoryFactory(String name) {
RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.get().getBean(name,
RepositoryFactoryInformation.class);
Class<?> domainType = ClassUtils
.getUserClass(repositoryFactoryInformation.getRepositoryInformation().getDomainType());
RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation();
Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1);
typesToRegister.add(domainType);
typesToRegister.addAll(alternativeDomainTypes);
for (Class<?> type : typesToRegister) {
cacheFirstOrPrimary(type, repositoryFactoryInformation, BeanFactoryUtils.transformedBeanName(name));
}
}
Returns whether we have a repository instance registered to manage instances of the given domain class.
Params: - domainClass – must not be null.
Returns:
/**
* Returns whether we have a repository instance registered to manage instances of the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
public boolean hasRepositoryFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
Class<?> userClass = ProxyUtils.getUserClass(domainClass);
return repositoryFactoryInfos.containsKey(userClass);
}
Returns the repository managing the given domain class.
Params: - domainClass – must not be null.
Returns:
/**
* Returns the repository managing the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
public Optional<Object> getRepositoryFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
Class<?> userClass = ProxyUtils.getUserClass(domainClass);
Optional<String> repositoryBeanName = Optional.ofNullable(repositoryBeanNames.get(userClass));
return beanFactory.flatMap(it -> repositoryBeanName.map(it::getBean));
}
Returns the RepositoryFactoryInformation
for the given domain class. The given code
is
converted to the actual user class if necessary, @see ProxyUtils#getUserClass.
Params: - domainClass – must not be null.
Returns: the RepositoryFactoryInformation
for the given domain class or null if no repository registered for this domain class.
/**
* Returns the {@link RepositoryFactoryInformation} for the given domain class. The given <code>code</code> is
* converted to the actual user class if necessary, @see ProxyUtils#getUserClass.
*
* @param domainClass must not be {@literal null}.
* @return the {@link RepositoryFactoryInformation} for the given domain class or {@literal null} if no repository
* registered for this domain class.
*/
private RepositoryFactoryInformation<Object, Object> getRepositoryFactoryInfoFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
Class<?> userType = ProxyUtils.getUserClass(domainClass);
RepositoryFactoryInformation<Object, Object> repositoryInfo = repositoryFactoryInfos.get(userType);
if (repositoryInfo != null) {
return repositoryInfo;
}
if (!userType.equals(Object.class)) {
return getRepositoryFactoryInfoFor(userType.getSuperclass());
}
return EMPTY_REPOSITORY_FACTORY_INFO;
}
Returns the EntityInformation
for the given domain class. Params: - domainClass – must not be null.
Returns:
/**
* Returns the {@link EntityInformation} for the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public <T, S> EntityInformation<T, S> getEntityInformationFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return (EntityInformation<T, S>) getRepositoryFactoryInfoFor(domainClass).getEntityInformation();
}
Returns the RepositoryInformation
for the given domain class. Params: - domainClass – must not be null.
Returns: the RepositoryInformation
for the given domain class or Optional#empty() if no repository registered for this domain class.
/**
* Returns the {@link RepositoryInformation} for the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return the {@link RepositoryInformation} for the given domain class or {@literal Optional#empty()} if no
* repository registered for this domain class.
*/
public Optional<RepositoryInformation> getRepositoryInformationFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
RepositoryFactoryInformation<Object, Object> information = getRepositoryFactoryInfoFor(domainClass);
return information == EMPTY_REPOSITORY_FACTORY_INFO ? Optional.empty()
: Optional.of(information.getRepositoryInformation());
}
Returns the RepositoryInformation
for the given domain type. Params: - domainType – must not be null.
Throws: - IllegalArgumentException – in case no
RepositoryInformation
could be found for the given domain type.
Returns: the RepositoryInformation
for the given domain type.
/**
* Returns the {@link RepositoryInformation} for the given domain type.
*
* @param domainType must not be {@literal null}.
* @return the {@link RepositoryInformation} for the given domain type.
* @throws IllegalArgumentException in case no {@link RepositoryInformation} could be found for the given domain type.
*/
public RepositoryInformation getRequiredRepositoryInformation(Class<?> domainType) {
return getRepositoryInformationFor(domainType).orElseThrow(() -> new IllegalArgumentException(
"No required RepositoryInformation found for domain type " + domainType.getName() + "!"));
}
Returns the RepositoryInformation
for the given repository interface. Params: - repositoryInterface – must not be null.
Returns: the RepositoryInformation
for the given repository interface or null there's no repository instance registered for the given interface. Since: 1.12
/**
* Returns the {@link RepositoryInformation} for the given repository interface.
*
* @param repositoryInterface must not be {@literal null}.
* @return the {@link RepositoryInformation} for the given repository interface or {@literal null} there's no
* repository instance registered for the given interface.
* @since 1.12
*/
public Optional<RepositoryInformation> getRepositoryInformation(Class<?> repositoryInterface) {
return repositoryFactoryInfos.values().stream()//
.map(RepositoryFactoryInformation::getRepositoryInformation)//
.filter(information -> information.getRepositoryInterface().equals(repositoryInterface))//
.findFirst();
}
Returns the PersistentEntity
for the given domain class. Might return null in case the module storing the given domain class does not support the mapping subsystem. Params: - domainClass – must not be null.
Returns: the PersistentEntity
for the given domain class or null if no repository is registered for the domain class or the repository is not backed by a MappingContext
implementation.
/**
* Returns the {@link PersistentEntity} for the given domain class. Might return {@literal null} in case the module
* storing the given domain class does not support the mapping subsystem.
*
* @param domainClass must not be {@literal null}.
* @return the {@link PersistentEntity} for the given domain class or {@literal null} if no repository is registered
* for the domain class or the repository is not backed by a {@link MappingContext} implementation.
*/
public PersistentEntity<?, ?> getPersistentEntity(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return getRepositoryFactoryInfoFor(domainClass).getPersistentEntity();
}
Returns the QueryMethod
s contained in the repository managing the given domain class. Params: - domainClass – must not be null.
Returns:
/**
* Returns the {@link QueryMethod}s contained in the repository managing the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
public List<QueryMethod> getQueryMethodsFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return getRepositoryFactoryInfoFor(domainClass).getQueryMethods();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<Class<?>> iterator() {
return repositoryFactoryInfos.keySet().iterator();
}
Caches the repository information for the given domain type or overrides existing information in case the bean name
points to a primary bean definition.
Params: - type – must not be null.
- information – must not be null.
- name – must not be null.
/**
* Caches the repository information for the given domain type or overrides existing information in case the bean name
* points to a primary bean definition.
*
* @param type must not be {@literal null}.
* @param information must not be {@literal null}.
* @param name must not be {@literal null}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void cacheFirstOrPrimary(Class<?> type, RepositoryFactoryInformation information, String name) {
if (repositoryBeanNames.containsKey(type)) {
Boolean presentAndPrimary = beanFactory //
.filter(ConfigurableListableBeanFactory.class::isInstance) //
.map(ConfigurableListableBeanFactory.class::cast) //
.map(it -> it.getBeanDefinition(name)) //
.map(BeanDefinition::isPrimary) //
.orElse(false);
if (!presentAndPrimary) {
return;
}
}
this.repositoryFactoryInfos.put(type, information);
this.repositoryBeanNames.put(type, name);
}
Null-object to avoid nasty null checks in cache lookups. Author: Thomas Darimont
/**
* Null-object to avoid nasty {@literal null} checks in cache lookups.
*
* @author Thomas Darimont
*/
private static enum EmptyRepositoryFactoryInformation implements RepositoryFactoryInformation<Object, Object> {
INSTANCE;
@Override
public EntityInformation<Object, Object> getEntityInformation() {
throw new UnsupportedOperationException();
}
@Override
public RepositoryInformation getRepositoryInformation() {
throw new UnsupportedOperationException();
}
@Override
public PersistentEntity<?, ?> getPersistentEntity() {
throw new UnsupportedOperationException();
}
@Override
public List<QueryMethod> getQueryMethods() {
return Collections.emptyList();
}
}
}