diff --git a/src/main/java/org/springframework/data/config/TypeFilterParser.java b/src/main/java/org/springframework/data/config/TypeFilterParser.java index 340b88038..c7a06ff77 100644 --- a/src/main/java/org/springframework/data/config/TypeFilterParser.java +++ b/src/main/java/org/springframework/data/config/TypeFilterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 the original author or authors. + * Copyright 2010-2014 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. @@ -82,7 +82,7 @@ public class TypeFilterParser { * @param type must not be {@literal null}. * @return */ - public Iterable parseTypeFilters(Element element, Type type) { + public Collection parseTypeFilters(Element element, Type type) { NodeList nodeList = element.getChildNodes(); Collection filters = new HashSet(); diff --git a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java index 411a9dbbf..df5f704a5 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -58,6 +58,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura private final AnnotationMetadata metadata; private final AnnotationAttributes attributes; private final ResourceLoader resourceLoader; + private final boolean hasExplicitFilters; /** * Creates a new {@link AnnotationRepositoryConfigurationSource} from the given {@link AnnotationMetadata} and @@ -80,6 +81,25 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); this.metadata = metadata; this.resourceLoader = resourceLoader; + this.hasExplicitFilters = hasExplicitFilters(attributes); + } + + /** + * Returns whether there's explicit configuration of include- or exclude filters. + * + * @param attributes must not be {@literal null}. + * @return + */ + private static boolean hasExplicitFilters(AnnotationAttributes attributes) { + + for (String attribute : Arrays.asList("includeFilters", "excludeFilters")) { + + if (attributes.getAnnotationArray(attribute).length > 0) { + return true; + } + } + + return false; } /* @@ -268,6 +288,15 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura return StringUtils.hasText(attribute) ? attribute : null; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#usesExplicitFilters() + */ + @Override + public boolean usesExplicitFilters() { + return hasExplicitFilters; + } + /** * Safely reads the {@code pattern} attribute from the given {@link AnnotationAttributes} and returns an empty list if * the attribute is not present. diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java index 6724dedc4..681bbab9d 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java @@ -60,12 +60,15 @@ public class RepositoryBeanDefinitionParser implements BeanDefinitionParser { Environment environment = parser.getDelegate().getEnvironment(); ResourceLoader resourceLoader = parser.getReaderContext().getResourceLoader(); + BeanDefinitionRegistry registry = parser.getRegistry(); XmlRepositoryConfigurationSource configSource = new XmlRepositoryConfigurationSource(element, parser, environment); RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configSource, resourceLoader, environment); - for (BeanComponentDefinition definition : delegate.registerRepositoriesIn(parser.getRegistry(), extension)) { + RepositoryConfigurationUtils.exposeRegistration(extension, registry, configSource); + + for (BeanComponentDefinition definition : delegate.registerRepositoriesIn(registry, extension)) { parser.getReaderContext().fireComponentRegistered(definition); } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java index 6a8fe16b7..183d75d6b 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java @@ -74,9 +74,13 @@ public abstract class RepositoryBeanDefinitionRegistrarSupport implements Import AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource( annotationMetadata, getAnnotation(), resourceLoader, environment); + RepositoryConfigurationExtension extension = getExtension(); + RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource); + RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, resourceLoader, environment); - delegate.registerRepositoriesIn(registry, getExtension()); + + delegate.registerRepositoriesIn(registry, extension); } /** diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index 8d7e307ee..237f37c6f 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -15,8 +15,7 @@ */ package org.springframework.data.repository.config; -import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*; - +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -27,11 +26,15 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.util.Assert; /** @@ -46,11 +49,16 @@ public class RepositoryConfigurationDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryConfigurationDelegate.class); + private static final String REPOSITORY_REGISTRATION = "Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}"; + private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode!"; + private static final String MODULE_DETECTION_PACKAGE = "org.springframework.data.**.repository.support"; + private final RepositoryConfigurationSource configurationSource; private final ResourceLoader resourceLoader; private final Environment environment; private final BeanNameGenerator generator; private final boolean isXml; + private final boolean inMultiStoreMode; /** * Creates a new {@link RepositoryConfigurationDelegate} for the given {@link RepositoryConfigurationSource} and @@ -77,6 +85,7 @@ public class RepositoryConfigurationDelegate { this.configurationSource = configurationSource; this.resourceLoader = resourceLoader; this.environment = defaultEnvironment(environment, resourceLoader); + this.inMultiStoreMode = multipleStoresDetected(); } /** @@ -107,8 +116,6 @@ public class RepositoryConfigurationDelegate { public List registerRepositoriesIn(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension) { - exposeRegistration(extension, registry); - extension.registerBeansForRoot(registry, configurationSource); RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension, resourceLoader, @@ -116,7 +123,7 @@ public class RepositoryConfigurationDelegate { List definitions = new ArrayList(); for (RepositoryConfiguration configuration : extension - .getRepositoryConfigurations(configurationSource, resourceLoader)) { + .getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) { BeanDefinitionBuilder definitionBuilder = builder.build(configuration); @@ -132,8 +139,8 @@ public class RepositoryConfigurationDelegate { String beanName = generator.generateBeanName(beanDefinition, registry); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Registering repository: " + beanName + " - Interface: " + configuration.getRepositoryInterface() - + " - Factory: " + extension.getRepositoryFactoryClassName()); + LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName, + configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName()); } registry.registerBeanDefinition(beanName, beanDefinition); @@ -144,29 +151,57 @@ public class RepositoryConfigurationDelegate { } /** - * Registeres the given {@link RepositoryConfigurationExtension} to indicate the repository configuration for a - * particular store (expressed through the extension's concrete type) has appened. Useful for downstream components - * that need to detect exactly that case. The bean definition is marked as lazy-init so that it doesn't get - * instantiated if no one really cares. + * Scans {@code repository.support} packages for implementations of {@link RepositoryFactorySupport}. Finding more + * than a single type is considered a multi-store configuration scenario which will trigger stricter repository + * scanning. * - * @param extension - * @param registry + * @return */ - private void exposeRegistration(RepositoryConfigurationExtension extension, BeanDefinitionRegistry registry) { + private boolean multipleStoresDetected() { - Class extensionType = extension.getClass(); - String beanName = extensionType.getName().concat(GENERATED_BEAN_NAME_SEPARATOR).concat("0"); + ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, + environment); + scanner.addIncludeFilter(new LenientAssignableTypeFilter(RepositoryFactorySupport.class)); + int numberOfModulesFound = scanner.findCandidateComponents(MODULE_DETECTION_PACKAGE).size(); - if (registry.containsBeanDefinition(beanName)) { - return; + if (numberOfModulesFound > 1) { + LOGGER.debug(MULTIPLE_MODULES); + return true; } - // Register extension as bean to indicate repository parsing and registration has happened - RootBeanDefinition definition = new RootBeanDefinition(extensionType); - definition.setSource(configurationSource.getSource()); - definition.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE); - definition.setLazyInit(true); + return false; + } - registry.registerBeanDefinition(beanName, definition); + /** + * Special {@link AssignableTypeFilter} that generally considers exceptions during type matching indicating a + * non-match. TODO: Remove after upgrade to Spring 4.0.7. + * + * @see https://jira.spring.io/browse/SPR-12042 + * @author Oliver Gierke + */ + private static class LenientAssignableTypeFilter extends AssignableTypeFilter { + + /** + * Creates a new {@link LenientAssignableTypeFilter} for the given target type. + * + * @param targetType must not be {@literal null}. + */ + public LenientAssignableTypeFilter(Class targetType) { + super(targetType); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory) + */ + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + + try { + return super.match(metadataReader, metadataReaderFactory); + } catch (Exception o_O) { + return false; + } + } } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java index b1c505805..b30a60ea7 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2014 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. @@ -30,16 +30,40 @@ import org.springframework.core.io.ResourceLoader; */ public interface RepositoryConfigurationExtension { + /** + * Returns the descriptive name of the module. + * + * @return + */ + String getModuleName(); + /** * Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}. * * @param configSource must not be {@literal null}. * @param loader must not be {@literal null}. + * @deprecated call or implement + * {@link #getRepositoryConfigurations(RepositoryConfigurationSource, ResourceLoader, boolean)} instead. * @return */ + @Deprecated Collection> getRepositoryConfigurations( T configSource, ResourceLoader loader); + /** + * Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}. + * + * @param configSource + * @param loader + * @param strictMatchesOnly whether to return strict repository matches only. Handing in {@literal true} will cause + * the repository interfaces and domain types handled to be checked whether they are managed by the current + * store. + * @return + * @since 1.9 + */ + Collection> getRepositoryConfigurations( + T configSource, ResourceLoader loader, boolean strictMatchesOnly); + /** * Returns the default location of the Spring Data named queries. * diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java index b22833c4e..1dc2a63ad 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -17,16 +17,24 @@ 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 @@ -37,14 +45,37 @@ import org.springframework.util.Assert; */ 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 {}."; + protected static final String REPOSITORY_INTERFACE_POST_PROCESSOR = "org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor"; + /* + * (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 Collection> 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 Collection> getRepositoryConfigurations( + T configSource, ResourceLoader loader, boolean strictMatchesOnly) { Assert.notNull(configSource); Assert.notNull(loader); @@ -52,8 +83,21 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit Set> result = new HashSet>(); for (BeanDefinition candidate : configSource.getCandidates(loader)) { - result.add(getRepositoryConfiguration(candidate, configSource)); + + RepositoryConfiguration configuration = getRepositoryConfiguration(candidate, configSource); + + if (!strictMatchesOnly || configSource.usesExplicitFilters()) { + result.add(configuration); + continue; + } + + Class repositoryInterface = loadRepositoryInterface(configuration, loader); + + if (repositoryInterface == null || isStrictRepositoryCandidate(repositoryInterface)) { + result.add(configuration); + } } + return result; } @@ -88,24 +132,29 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit * (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) { - - } + 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) { - - } + 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) { + 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> getIdentifyingAnnotations() { + return Collections.emptySet(); } /** @@ -155,4 +204,61 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit BeanDefinition definition, T configSource) { return new DefaultRepositoryConfiguration(configSource, definition); } + + /** + * Returns whether the given repository interface 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(Class repositoryInterface) { + + RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface); + Class domainType = metadata.getDomainType(); + + Collection> annotations = getIdentifyingAnnotations(); + + if (annotations.isEmpty()) { + return true; + } + + for (Class annotationType : annotations) { + if (AnnotationUtils.findAnnotation(domainType, annotationType) != null) { + return true; + } + } + + LOGGER.debug(MULTI_STORE_DROPPED, getModuleName(), repositoryInterface); + + return false; + } + + /** + * 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 static 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 e) { + LOGGER.warn(String.format(CLASS_LOADING_ERROR, repositoryInterface, classLoader), e); + } catch (LinkageError e) { + LOGGER.warn(String.format(CLASS_LOADING_ERROR, repositoryInterface, classLoader), e); + } + + return null; + } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java index 82b9ad2b1..612cd33fd 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -88,4 +88,12 @@ public interface RepositoryConfigurationSource { * @since 1.8 */ String getAttribute(String name); + + /** + * Returns whether the configuration uses explicit filtering to scan for repository types. + * + * @return whether the configuration uses explicit filtering to scan for repository types. + * @since 1.9 + */ + boolean usesExplicitFilters(); } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java new file mode 100644 index 000000000..58b40c288 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014 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 org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.util.Assert; + +/** + * Helper class to centralize common functionality that needs to be used in various places of the configuration + * implementation. + * + * @author Oliver Gierke + */ +public class RepositoryConfigurationUtils { + + /** + * Registeres the given {@link RepositoryConfigurationExtension} to indicate the repository configuration for a + * particular store (expressed through the extension's concrete type) has appened. Useful for downstream components + * that need to detect exactly that case. The bean definition is marked as lazy-init so that it doesn't get + * instantiated if no one really cares. + * + * @param extension must not be {@literal null}. + * @param registry must not be {@literal null}. + * @param configurationSource must not be {@literal null}. + */ + public static void exposeRegistration(RepositoryConfigurationExtension extension, BeanDefinitionRegistry registry, + RepositoryConfigurationSource configurationSource) { + + Assert.notNull(extension, "RepositoryConfigurationExtension must not be null!"); + Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); + Assert.notNull(configurationSource, "RepositoryConfigurationSource must not be null!"); + + Class extensionType = extension.getClass(); + String beanName = extensionType.getName().concat(GENERATED_BEAN_NAME_SEPARATOR).concat("0"); + + if (registry.containsBeanDefinition(beanName)) { + return; + } + + // Register extension as bean to indicate repository parsing and registration has happened + RootBeanDefinition definition = new RootBeanDefinition(extensionType); + definition.setSource(configurationSource.getSource()); + definition.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE); + definition.setLazyInit(true); + + registry.registerBeanDefinition(beanName, definition); + } +} diff --git a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java index 0318a33fe..2f1f0bc4c 100644 --- a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java @@ -16,6 +16,7 @@ package org.springframework.data.repository.config; import java.util.Arrays; +import java.util.Collection; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.env.Environment; @@ -46,8 +47,8 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou private final Element element; private final ParserContext context; - private final Iterable includeFilters; - private final Iterable excludeFilters; + private final Collection includeFilters; + private final Collection excludeFilters; /** * Creates a new {@link XmlRepositoryConfigurationSource} using the given {@link Element} and {@link ParserContext}. @@ -175,4 +176,13 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou return StringUtils.hasText(attribute) ? attribute : null; } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#usesExplicitFilters() + */ + @Override + public boolean usesExplicitFilters() { + return !(this.includeFilters.isEmpty() && this.excludeFilters.isEmpty()); + } } diff --git a/src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java b/src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java index ab4136a01..9f0d849f3 100644 --- a/src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java +++ b/src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.CrudMethods; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.util.QueryExecutionConverters; @@ -52,6 +53,21 @@ public abstract class AbstractRepositoryMetadata implements RepositoryMetadata { this.typeInformation = ClassTypeInformation.from(repositoryInterface); } + /** + * Creates a new {@link RepositoryMetadata} for the given repsository interface. + * + * @param repositoryInterface must not be {@literal null}. + * @since 1.9 + * @return + */ + public static RepositoryMetadata getMetadata(Class repositoryInterface) { + + Assert.notNull(repositoryInterface, "Repository interface must not be null!"); + + return Repository.class.isAssignableFrom(repositoryInterface) ? new DefaultRepositoryMetadata(repositoryInterface) + : new AnnotationRepositoryMetadata(repositoryInterface); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.core.RepositoryMetadata#getReturnedDomainClass(java.lang.reflect.Method) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index a164e053e..19a1a5ee5 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2014 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. @@ -201,8 +201,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { * @return */ RepositoryMetadata getRepositoryMetadata(Class repositoryInterface) { - return Repository.class.isAssignableFrom(repositoryInterface) ? new DefaultRepositoryMetadata(repositoryInterface) - : new AnnotationRepositoryMetadata(repositoryInterface); + return AbstractRepositoryMetadata.getMetadata(repositoryInterface); } /** diff --git a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java index c4d2a3508..94a719536 100644 --- a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java @@ -23,6 +23,8 @@ import java.util.Collection; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; @@ -127,16 +129,26 @@ public class AnnotationRepositoryConfigurationSourceUnitTests { public void returnsEmptyStringForBasePackage() throws Exception { StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(getClass().getClassLoader().loadClass( - "TypeInDefaultPackage")); + "TypeInDefaultPackage"), true); RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, EnableRepositories.class, resourceLoader, environment); assertThat(configurationSource.getBasePackages(), hasItem("")); } + /** + * @see DATACMNS-526 + */ + @Test + public void detectsExplicitFilterConfiguration() { + + assertThat(getConfigSource(ConfigurationWithExplicitFilter.class).usesExplicitFilters(), is(true)); + assertThat(getConfigSource(DefaultConfiguration.class).usesExplicitFilters(), is(false)); + } + private AnnotationRepositoryConfigurationSource getConfigSource(Class type) { - AnnotationMetadata metadata = new StandardAnnotationMetadata(type); + AnnotationMetadata metadata = new StandardAnnotationMetadata(type, true); return new AnnotationRepositoryConfigurationSource(metadata, EnableRepositories.class, resourceLoader, environment); } @@ -150,4 +162,7 @@ public class AnnotationRepositoryConfigurationSourceUnitTests { @EnableRepositories(considerNestedRepositories = true) static class DefaultConfigurationWithNestedRepositories {} + + @EnableRepositories(excludeFilters = { @Filter(Primary.class) }) + static class ConfigurationWithExplicitFilter {} } diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java index 234a4a881..3a866218a 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -74,7 +74,7 @@ public class RepositoryComponentProviderUnitTests { Set components = provider.findCandidateComponents("org.springframework.data.repository.config"); String nestedRepositoryClassName = "org.springframework.data.repository.config.RepositoryComponentProviderUnitTests$MyNestedRepository"; - assertThat(components.size(), is(3)); + assertThat(components.size(), is(greaterThanOrEqualTo(1))); assertThat(components, Matchers. hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName)))); } diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java new file mode 100644 index 000000000..423202e7c --- /dev/null +++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2014 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.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; +import org.springframework.context.annotation.Primary; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; + +/** + * Unit tests for {@link RepositoryConfigurationExtensionSupport}. + * + * @author Oliver Gierke + */ +public class RepositoryConfigurationExtensionSupportUnitTests { + + RepositoryConfigurationExtensionSupport extension = new SampleRepositoryConfigurationExtension(); + + /** + * @see DATACMNS-526 + */ + @Test + public void doesNotConsiderRepositoryForPlainTypeStrictMatch() { + assertThat(extension.isStrictRepositoryCandidate(PlainTypeRepository.class), is(false)); + } + + /** + * @see DATACMNS-526 + */ + @Test + public void considersRepositoryWithAnnotatedTypeStrictMatch() { + assertThat(extension.isStrictRepositoryCandidate(AnnotatedTypeRepository.class), is(true)); + } + + static class SampleRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() + */ + @Override + protected String getModulePrefix() { + return "core"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryClassName() + */ + @Override + public String getRepositoryFactoryClassName() { + return RepositoryFactorySupport.class.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations() + */ + @Override + protected Collection> getIdentifyingAnnotations() { + return Collections.> singleton(Primary.class); + } + } + + @Primary + static class AnnotatedType {} + + static class PlainType {} + + interface AnnotatedTypeRepository extends Repository {} + + interface PlainTypeRepository extends Repository {} +}