/*
 * 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:
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 QueryMethods 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(); } } }