DATACMNS-526 - Improved configuration behavior in multi-store scenarios.

We now apply a stricter repository interface selection if we detect multiple Spring Data modules to be in the classpath to avoid the repository scanning accidentally overriding each others definitions or even picking up interfaces they weren't intended to manage.

The detection is based on a type scan in dedicated base package where subtypes of RepositoryFactorySupport usually reside. If more than one type is found, we activate strict scanning.

The strict check is actually implemented in RepositoryConfigurationExtensionSupport to be accessible for store implementations. By default we try to load the repository candidate interface and inspect the managed domain types for a collection of annotations (see RepositoryConfigurationExtensionSupport.getIdentifyingAnnotations()). Implementors still have the chance to customize the behavior by overriding isStrictRepositoryCandidate(…).

We also introduced a getModuleName() to be able to create better logging output in terms of repository registration. Moved registration of RepositoryConfigurationExtension as Spring bean into the base types for XML and annotation configuration support to make sure they only kick in if explicit configuration is used.
This commit is contained in:
Oliver Gierke
2014-07-28 19:42:10 +02:00
parent 3bcf6f3f27
commit 81af5ee730
15 changed files with 455 additions and 48 deletions

View File

@@ -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<TypeFilter> parseTypeFilters(Element element, Type type) {
public Collection<TypeFilter> parseTypeFilters(Element element, Type type) {
NodeList nodeList = element.getChildNodes();
Collection<TypeFilter> filters = new HashSet<TypeFilter>();

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);
}
/**

View File

@@ -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<BeanComponentDefinition> 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<BeanComponentDefinition> definitions = new ArrayList<BeanComponentDefinition>();
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> 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<? extends RepositoryConfigurationExtension> 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;
}
}
}
}

View File

@@ -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
<T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> 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
*/
<T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(
T configSource, ResourceLoader loader, boolean strictMatchesOnly);
/**
* Returns the default location of the Spring Data named queries.
*

View File

@@ -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 <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);
Assert.notNull(loader);
@@ -52,8 +83,21 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
Set<RepositoryConfiguration<T>> result = new HashSet<RepositoryConfiguration<T>>();
for (BeanDefinition candidate : configSource.getCandidates(loader)) {
result.add(getRepositoryConfiguration(candidate, configSource));
RepositoryConfiguration<T> 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<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.emptySet();
}
/**
@@ -155,4 +204,61 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
BeanDefinition definition, T configSource) {
return new DefaultRepositoryConfiguration<T>(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<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.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;
}
}

View File

@@ -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();
}

View File

@@ -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<? extends RepositoryConfigurationExtension> 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);
}
}

View File

@@ -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<TypeFilter> includeFilters;
private final Iterable<TypeFilter> excludeFilters;
private final Collection<TypeFilter> includeFilters;
private final Collection<TypeFilter> 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());
}
}

View File

@@ -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)

View File

@@ -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);
}
/**

View File

@@ -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 {}
}

View File

@@ -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<BeanDefinition> 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.<BeanDefinition> hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName))));
}

View File

@@ -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<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.<Class<? extends Annotation>> singleton(Primary.class);
}
}
@Primary
static class AnnotatedType {}
static class PlainType {}
interface AnnotatedTypeRepository extends Repository<AnnotatedType, Long> {}
interface PlainTypeRepository extends Repository<PlainType, Long> {}
}