/* * Copyright 2012-2017 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 * * http://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.config; import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.ResourceLoader; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Base implementation of {@link RepositoryConfigurationExtension} to ease the implementation of the interface. Will * default the default named query location based on a module prefix provided by implementors (see * {@link #getModulePrefix()}). Stubs out the post-processing methods as they might not be needed by default. * * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl */ public abstract class RepositoryConfigurationExtensionSupport implements RepositoryConfigurationExtension { private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryConfigurationExtensionSupport.class); private static final String CLASS_LOADING_ERROR = "%s - Could not load type %s using class loader %s."; private static final String MULTI_STORE_DROPPED = "Spring Data {} - Could not safely identify store assignment for repository candidate {}."; /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName() */ @Override public String getModuleName() { return StringUtils.capitalize(getModulePrefix()); } /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryConfigurations(org.springframework.data.repository.config.RepositoryConfigurationSource, org.springframework.core.io.ResourceLoader) */ public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations( T configSource, ResourceLoader loader) { return getRepositoryConfigurations(configSource, loader, false); } /* * * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryConfigurations(org.springframework.data.repository.config.RepositoryConfigurationSource, org.springframework.core.io.ResourceLoader, boolean) */ public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations( T configSource, ResourceLoader loader, boolean strictMatchesOnly) { Assert.notNull(configSource, "ConfigSource must not be null!"); Assert.notNull(loader, "Loader must not be null!"); Set<RepositoryConfiguration<T>> result = new HashSet<>(); for (BeanDefinition candidate : configSource.getCandidates(loader)) { RepositoryConfiguration<T> configuration = getRepositoryConfiguration(candidate, configSource); Class<?> repositoryInterface = loadRepositoryInterface(configuration, loader); if (repositoryInterface == null) { result.add(configuration); continue; } RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface); if (!useRepositoryConfiguration(metadata)) { continue; } if (!strictMatchesOnly || configSource.usesExplicitFilters()) { result.add(configuration); continue; } if (isStrictRepositoryCandidate(metadata)) { result.add(configuration); } } return result; } /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getDefaultNamedQueryLocation() */ public String getDefaultNamedQueryLocation() { return String.format("classpath*:META-INF/%s-named-queries.properties", getModulePrefix()); } /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource) */ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {} /** * Returns the prefix of the module to be used to create the default location for Spring Data named queries. * * @return must not be {@literal null}. */ protected abstract String getModulePrefix(); /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource) */ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {} /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) */ public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {} /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.XmlRepositoryConfigurationSource) */ public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {} /** * Return the annotations to scan domain types for when evaluating repository interfaces for store assignment. Modules * should return the annotations that identify a domain type as managed by the store explicitly. * * @return * @since 1.9 */ protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() { return Collections.emptySet(); } /** * Returns the types that indicate a store match when inspecting repositories for strict matches. * * @return * @since 1.9 */ protected Collection<Class<?>> getIdentifyingTypes() { return Collections.emptySet(); } /** * Sets the given source on the given {@link AbstractBeanDefinition} and registers it inside the given * {@link BeanDefinitionRegistry}. For {@link BeanDefinition}s to be registerd once-and-only-once for all * configuration elements (annotation or XML), prefer calling * {@link #registerIfNotAlreadyRegistered(AbstractBeanDefinition, BeanDefinitionRegistry, String, Object)} with a * dedicated bean name to avoid the bead definition being registered multiple times. * * * @param registry must not be {@literal null}. * @param bean must not be {@literal null}. * @param source must not be {@literal null}. * @return the bean name generated for the given {@link BeanDefinition} */ public static String registerWithSourceAndGeneratedBeanName(BeanDefinitionRegistry registry, AbstractBeanDefinition bean, Object source) { bean.setSource(source); String beanName = generateBeanName(bean, registry); registry.registerBeanDefinition(beanName, bean); return beanName; } /** * Registers the given {@link AbstractBeanDefinition} with the given registry with the given bean name unless the * registry already contains a bean with that name. * * @param bean must not be {@literal null}. * @param registry must not be {@literal null}. * @param beanName must not be {@literal null} or empty. * @param source must not be {@literal null}. */ public static void registerIfNotAlreadyRegistered(AbstractBeanDefinition bean, BeanDefinitionRegistry registry, String beanName, Object source) { if (registry.containsBeanDefinition(beanName)) { return; } bean.setSource(source); registry.registerBeanDefinition(beanName, bean); } /** * Returns whether the given {@link BeanDefinitionRegistry} already contains a bean of the given type assuming the * bean name has been autogenerated. * * @param type * @param registry * @return */ public static boolean hasBean(Class<?> type, BeanDefinitionRegistry registry) { String name = String.format("%s%s0", type.getName(), GENERATED_BEAN_NAME_SEPARATOR); return registry.containsBeanDefinition(name); } /** * Creates a actual {@link RepositoryConfiguration} instance for the given {@link RepositoryConfigurationSource} and * interface name. Defaults to the {@link DefaultRepositoryConfiguration} but allows sub-classes to override this to * customize the behavior. * * @param definition will never be {@literal null} or empty. * @param configSource will never be {@literal null}. * @return */ protected <T extends RepositoryConfigurationSource> RepositoryConfiguration<T> getRepositoryConfiguration( BeanDefinition definition, T configSource) { return new DefaultRepositoryConfiguration<>(configSource, definition, this); } /** * Returns whether the given repository metadata is a candidate for bean definition creation in the strict repository * detection mode. The default implementation inspects the domain type managed for a set of well-known annotations * (see {@link #getIdentifyingAnnotations()}). If none of them is found, the candidate is discarded. Implementations * should make sure, the only return {@literal true} if they're really sure the interface handed to the method is * really a store interface. * * @param repositoryInterface * @return * @since 1.9 */ protected boolean isStrictRepositoryCandidate(RepositoryMetadata metadata) { Collection<Class<?>> types = getIdentifyingTypes(); Class<?> repositoryInterface = metadata.getRepositoryInterface(); for (Class<?> type : types) { if (type.isAssignableFrom(repositoryInterface)) { return true; } } Class<?> domainType = metadata.getDomainType(); Collection<Class<? extends Annotation>> annotations = getIdentifyingAnnotations(); if (annotations.isEmpty()) { return true; } for (Class<? extends Annotation> annotationType : annotations) { if (AnnotationUtils.findAnnotation(domainType, annotationType) != null) { return true; } } LOGGER.info(MULTI_STORE_DROPPED, getModuleName(), repositoryInterface); return false; } /** * Return whether to use the configuration for the repository with the given metadata. Defaults to {@literal true}. * * @param metadata will never be {@literal null}. * @return */ protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) { return true; } /** * Loads the repository interface contained in the given {@link RepositoryConfiguration} using the given * {@link ResourceLoader}. * * @param configuration must not be {@literal null}. * @param loader must not be {@literal null}. * @return the repository interface or {@literal null} if it can't be loaded. */ private Class<?> loadRepositoryInterface(RepositoryConfiguration<?> configuration, ResourceLoader loader) { String repositoryInterface = configuration.getRepositoryInterface(); ClassLoader classLoader = loader.getClassLoader(); try { return org.springframework.util.ClassUtils.forName(repositoryInterface, classLoader); } catch (ClassNotFoundException | LinkageError e) { LOGGER.warn(String.format(CLASS_LOADING_ERROR, getModuleName(), repositoryInterface, classLoader), e); } return null; } }