From 96795f2980c07bfb1f9c5b32dc49ffa38416f48f Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 5 Jul 2012 17:16:03 +0200 Subject: [PATCH] DATACMNS-47 - Add support for JavaConfig based repository configuration. Completely rewrote namespace bean definition parsing to be extendable more easily. Separated XML concerns from annotation based configuration. The central point to extend the namespace parsing for a certain module is now hidden behind the RepositoryConfigurationExtension interface (have a look at the RepositoryConfigurationExtensionSupport base class as well). --- ...tractRepositoryConfigDefinitionParser.java | 495 ------------------ ...notationRepositoryConfigurationSource.java | 220 ++++++++ .../AutomaticRepositoryConfigInformation.java | 71 --- .../CommonRepositoryConfigInformation.java | 78 --- .../DefaultRepositoryConfiguration.java | 151 ++++++ .../GlobalRepositoryConfigInformation.java | 54 -- .../ManualRepositoryConfigInformation.java | 170 ------ .../NamedQueriesBeanDefinitionBuilder.java | 87 +++ ...DelegatingRepositoryConfigInformation.java | 186 ------- .../RepositoryBeanDefinitionBuilder.java | 172 ++++++ .../RepositoryBeanDefinitionParser.java | 132 +++++ ...ositoryBeanDefinitionRegistrarSupport.java | 81 +++ .../config/RepositoryComponentProvider.java | 231 ++++++++ .../repository/config/RepositoryConfig.java | 206 -------- .../config/RepositoryConfiguration.java | 99 ++++ .../RepositoryConfigurationExtension.java | 83 +++ ...positoryConfigurationExtensionSupport.java | 149 ++++++ .../config/RepositoryConfigurationSource.java | 79 +++ .../RepositoryConfigurationSourceSupport.java | 81 +++ .../SingleRepositoryConfigInformation.java | 67 --- .../repository/config/TypeFilterParser.java | 78 ++- .../XmlRepositoryConfigurationSource.java | 146 ++++++ .../data/repository/config/package-info.java | 2 +- .../repository/query/QueryLookupStrategy.java | 2 +- .../config/spring-repository-1.4.xsd | 15 - ...epositoryConfigurationSourceUnitTests.java | 95 ++++ ...faultRepositoryConfigurationUnitTests.java | 51 ++ .../repository/config/EnableRepositories.java | 42 ++ .../repository/config/MyOtherRepository.java | 23 + .../data/repository/config/MyRepository.java | 23 + ...itionRegistrarSupportIntegrationTests.java | 84 +++ .../RepositoryComponentProviderUnitTests.java | 23 +- .../config/SampleConfiguration.java | 24 + .../config/TypeFilterParserUnitTests.java | 39 +- src/docbkx/repositories.xml | 46 +- src/docbkx/repository-namespace-reference.xml | 62 +-- 36 files changed, 2176 insertions(+), 1471 deletions(-) delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java delete mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/EnableRepositories.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyOtherRepository.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyRepository.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/repository/config/SampleConfiguration.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java deleted file mode 100644 index b601b4536..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright 2008-2011 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 static org.springframework.data.repository.util.ClassUtils.*; - -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.annotation.Inherited; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.parsing.ReaderContext; -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.xml.BeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.classreading.MetadataReader; -import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; -import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.core.type.filter.RegexPatternTypeFilter; -import org.springframework.data.repository.NoRepositoryBean; -import org.springframework.data.repository.RepositoryDefinition; -import org.springframework.util.StringUtils; -import org.w3c.dom.Element; - -/** - * Base class to implement repository namespaces. These will typically consist of a main XML element potentially having - * child elements. The parser will wrap the XML element into a {@link GlobalRepositoryConfigInformation} object and - * allow either manual configuration or automatic detection of repository interfaces. - * - * @author Oliver Gierke - */ -public abstract class AbstractRepositoryConfigDefinitionParser, T extends SingleRepositoryConfigInformation> - implements BeanDefinitionParser { - - private static final Log LOG = LogFactory.getLog(AbstractRepositoryConfigDefinitionParser.class); - - private static final String REPOSITORY_INTERFACE_POST_PROCESSOR = "org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor"; - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) - */ - public BeanDefinition parse(Element element, ParserContext parser) { - - try { - S configContext = getGlobalRepositoryConfigInformation(element); - - if (configContext.configureManually()) { - doManualConfiguration(configContext, parser); - } else { - doAutoConfiguration(configContext, parser); - } - - Object beanSource = parser.extractSource(element); - registerBeansForRoot(parser.getRegistry(), beanSource); - - } catch (RuntimeException e) { - handleError(e, element, parser.getReaderContext()); - } - - return null; - } - - /** - * Executes repository auto configuration by scanning the provided base package for repository interfaces. - * - * @param config - * @param parser - */ - private void doAutoConfiguration(S config, ParserContext parser) { - - LOG.debug("Triggering auto repository detection"); - - ResourceLoader resourceLoader = parser.getReaderContext().getResourceLoader(); - - // Detect available repository interfaces - Set repositoryInterfaces = getRepositoryInterfacesForAutoConfig(config, resourceLoader, - parser.getReaderContext()); - - for (String repositoryInterface : repositoryInterfaces) { - registerGenericRepositoryFactoryBean(parser, config.getAutoconfigRepositoryInformation(repositoryInterface)); - } - } - - private Set getRepositoryInterfacesForAutoConfig(S config, ResourceLoader loader, ReaderContext reader) { - - ClassPathScanningCandidateComponentProvider scanner = new RepositoryComponentProvider( - config.getRepositoryBaseInterface()); - scanner.setResourceLoader(loader); - - TypeFilterParser parser = new TypeFilterParser(loader.getClassLoader(), reader); - parser.parseFilters(config.getSource(), scanner); - - Set findCandidateComponents = scanner.findCandidateComponents(config.getBasePackage()); - - Set interfaceNames = new HashSet(); - for (BeanDefinition definition : findCandidateComponents) { - interfaceNames.add(definition.getBeanClassName()); - } - - return interfaceNames; - } - - /** - * Returns a {@link GlobalRepositoryConfigInformation} implementation for the given element. - * - * @param element - * @return - */ - protected abstract S getGlobalRepositoryConfigInformation(Element element); - - /** - * Proceeds manual configuration by traversing the context's {@link SingleRepositoryConfigInformation}s. - * - * @param context - * @param parser - */ - private void doManualConfiguration(S context, ParserContext parser) { - - LOG.debug("Triggering manual repository detection"); - - for (T repositoryContext : context.getSingleRepositoryConfigInformations()) { - registerGenericRepositoryFactoryBean(parser, repositoryContext); - } - } - - private void handleError(Exception e, Element source, ReaderContext reader) { - - reader.error(e.getMessage(), reader.extractSource(source), e.getCause()); - } - - /** - * Registers a generic repository factory bean for a bean with the given name and the provided configuration context. - * - * @param parser - * @param name - * @param context - */ - private void registerGenericRepositoryFactoryBean(ParserContext parser, T context) { - - try { - - Object beanSource = parser.extractSource(context.getSource()); - - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(context - .getRepositoryFactoryBeanClassName()); - - builder.addPropertyValue("repositoryInterface", context.getInterfaceName()); - builder.addPropertyValue("queryLookupStrategyKey", context.getQueryLookupStrategyKey()); - builder.addPropertyValue("namedQueries", - new NamedQueriesBeanDefinitionParser(context.getNamedQueriesLocation()).parse(context.getSource(), parser)); - - String customImplementationBeanName = registerCustomImplementation(context, parser, beanSource); - - if (customImplementationBeanName != null) { - builder.addPropertyReference("customImplementation", customImplementationBeanName); - } - - postProcessBeanDefinition(context, builder, parser.getRegistry(), beanSource); - - AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); - beanDefinition.setSource(beanSource); - - if (LOG.isDebugEnabled()) { - LOG.debug("Registering repository: " + context.getBeanId() + " - Interface: " + context.getInterfaceName() - + " - Factory: " + context.getRepositoryFactoryBeanClassName() + ", - Custom implementation: " - + customImplementationBeanName); - } - - BeanComponentDefinition definition = new BeanComponentDefinition(beanDefinition, context.getBeanId()); - parser.registerBeanComponent(definition); - } catch (RuntimeException e) { - handleError(e, context.getSource(), parser.getReaderContext()); - } - } - - /** - * Callback to post process a repository bean definition prior to actual registration. - * - * @param context - * @param builder - * @param beanSource - */ - protected void postProcessBeanDefinition(T context, BeanDefinitionBuilder builder, BeanDefinitionRegistry registry, - Object beanSource) { - - } - - /** - * Registers a possibly available custom repository implementation on the repository bean. Tries to find an already - * registered bean to reference or tries to detect a custom implementation itself. - * - * @param config - * @param parser - * @param source - * @return the bean name of the custom implementation or {@code null} if none available - */ - private String registerCustomImplementation(T config, ParserContext parser, Object source) { - - String beanName = config.getImplementationBeanName(); - - // Already a bean configured? - if (parser.getRegistry().containsBeanDefinition(beanName)) { - return beanName; - } - - // Autodetect implementation - if (config.autodetectCustomImplementation()) { - - AbstractBeanDefinition beanDefinition = detectCustomImplementation(config, parser); - - if (null == beanDefinition) { - return null; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Registering custom repository implementation: " + config.getImplementationBeanName() + " " - + beanDefinition.getBeanClassName()); - } - - beanDefinition.setSource(source); - parser.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName)); - - } else { - beanName = config.getCustomImplementationRef(); - } - - return beanName; - } - - /** - * Tries to detect a custom implementation for a repository bean by classpath scanning. - * - * @param config - * @param parser - * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found - */ - private AbstractBeanDefinition detectCustomImplementation(T config, ParserContext parser) { - - // Build pattern to lookup implementation class - Pattern pattern = Pattern.compile(".*\\." + config.getImplementationClassName()); - - // Build classpath scanner and lookup bean definition - ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); - provider.setResourceLoader(parser.getReaderContext().getResourceLoader()); - provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); - Set definitions = provider.findCandidateComponents(config.getBasePackage()); - - if (definitions.size() == 0) { - return null; - } - - if (definitions.size() == 1) { - return (AbstractBeanDefinition) definitions.iterator().next(); - } - - List implementationClassNames = new ArrayList(); - for (BeanDefinition bean : definitions) { - implementationClassNames.add(bean.getBeanClassName()); - } - - throw new IllegalStateException(String.format( - "Ambiguous custom implementations detected! Found %s but expected a single implementation!", - StringUtils.collectionToCommaDelimitedString(implementationClassNames))); - } - - /** - * Callback to register additional bean definitions for a {@literal repositories} root node. This usually includes - * beans you have to set up once independently of the number of repositories to be created. Will be called before any - * repositories bean definitions have been registered. - * - * @param registry - * @param source - */ - protected void registerBeansForRoot(BeanDefinitionRegistry registry, Object source) { - - AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(REPOSITORY_INTERFACE_POST_PROCESSOR) - .getBeanDefinition(); - - registerWithSourceAndGeneratedBeanName(registry, definition, source); - } - - /** - * 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 - */ - protected static boolean hasBean(Class type, BeanDefinitionRegistry registry) { - - String name = String.format("%s%s0", type.getName(), GENERATED_BEAN_NAME_SEPARATOR); - return registry.containsBeanDefinition(name); - } - - /** - * Sets the given source on the given {@link AbstractBeanDefinition} and registers it inside the given - * {@link BeanDefinitionRegistry}. - * - * @param registry - * @param bean - * @param source - * @return - */ - protected static String registerWithSourceAndGeneratedBeanName(BeanDefinitionRegistry registry, - AbstractBeanDefinition bean, Object source) { - - bean.setSource(source); - - String beanName = generateBeanName(bean, registry); - registry.registerBeanDefinition(beanName, bean); - - return beanName; - } - - /** - * Custom {@link ClassPathScanningCandidateComponentProvider} scanning for interfaces extending the given base - * interface. Skips interfaces annotated with {@link NoRepositoryBean}. - * - * @author Oliver Gierke - */ - static class RepositoryComponentProvider extends ClassPathScanningCandidateComponentProvider { - - /** - * Creates a new {@link RepositoryComponentProvider}. - * - * @param repositoryInterface the interface to scan for - */ - public RepositoryComponentProvider(Class repositoryInterface) { - - super(false); - addIncludeFilter(new InterfaceTypeFilter(repositoryInterface)); - addIncludeFilter(new AnnotationTypeFilter(RepositoryDefinition.class, true, true)); - addExcludeFilter(new AnnotationTypeFilter(NoRepositoryBean.class)); - } - - /* - * (non-Javadoc) - * - * @seeorg.springframework.context.annotation. - * ClassPathScanningCandidateComponentProvider - * #isCandidateComponent(org.springframework - * .beans.factory.annotation.AnnotatedBeanDefinition) - */ - @Override - protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { - - boolean isNonRepositoryInterface = !isGenericRepositoryInterface(beanDefinition.getBeanClassName()); - boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass(); - - return isNonRepositoryInterface && isTopLevelType; - } - - /** - * {@link org.springframework.core.type.filter.TypeFilter} that only matches interfaces. Thus setting this up makes - * only sense providing an interface type as {@code targetType}. - * - * @author Oliver Gierke - */ - private static class InterfaceTypeFilter extends AssignableTypeFilter { - - /** - * Creates a new {@link InterfaceTypeFilter}. - * - * @param targetType - */ - public InterfaceTypeFilter(Class targetType) { - - super(targetType); - } - - /* - * (non-Javadoc) - * - * @seeorg.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 { - - return metadataReader.getClassMetadata().isInterface() && super.match(metadataReader, metadataReaderFactory); - } - } - - // Copy of Spring's AnnotationTypeFilter until SPR-8336 gets resolved. - - /** - * A simple filter which matches classes with a given annotation, checking inherited annotations as well. - * - *

- * The matching logic mirrors that of Class.isAnnotationPresent(). - * - * @author Mark Fisher - * @author Ramnivas Laddad - * @author Juergen Hoeller - * @since 2.5 - */ - private static class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter { - - private final Class annotationType; - - private final boolean considerMetaAnnotations; - - /** - * Create a new AnnotationTypeFilter for the given annotation type. This filter will also match meta-annotations. - * To disable the meta-annotation matching, use the constructor that accepts a ' - * considerMetaAnnotations' argument. The filter will not match interfaces. - * - * @param annotationType the annotation type to match - */ - public AnnotationTypeFilter(Class annotationType) { - this(annotationType, true); - } - - /** - * Create a new AnnotationTypeFilter for the given annotation type. The filter will not match interfaces. - * - * @param annotationType the annotation type to match - * @param considerMetaAnnotations whether to also match on meta-annotations - */ - public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations) { - this(annotationType, considerMetaAnnotations, false); - } - - /** - * Create a new {@link AnnotationTypeFilter} for the given annotation type. - * - * @param annotationType the annotation type to match - * @param considerMetaAnnotations whether to also match on meta-annotations - * @param considerInterfaces whether to also match interfaces - */ - public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations, - boolean considerInterfaces) { - super(annotationType.isAnnotationPresent(Inherited.class), considerInterfaces); - this.annotationType = annotationType; - this.considerMetaAnnotations = considerMetaAnnotations; - } - - @Override - protected boolean matchSelf(MetadataReader metadataReader) { - AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - return metadata.hasAnnotation(this.annotationType.getName()) - || (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); - } - - @Override - protected Boolean matchSuperClass(String superClassName) { - if (Object.class.getName().equals(superClassName)) { - return Boolean.FALSE; - } else if (superClassName.startsWith("java.")) { - try { - Class clazz = getClass().getClassLoader().loadClass(superClassName); - return (clazz.getAnnotation(this.annotationType) != null); - } catch (ClassNotFoundException ex) { - // Class not found - can't determine a match that way. - } - } - return null; - } - } - } -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java new file mode 100644 index 000000000..6023be867 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -0,0 +1,220 @@ +/* + * Copyright 2012 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 java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Annotation based {@link RepositoryConfigurationSource}. + * + * @author Oliver Gierke + */ +public class AnnotationRepositoryConfigurationSource extends RepositoryConfigurationSourceSupport { + + private static final String REPOSITORY_IMPLEMENTATION_POSTFIX = "repositoryImplementationPostfix"; + private static final String BASE_PACKAGES = "basePackages"; + private static final String BASE_PACKAGE_CLASSES = "basePackageClasses"; + private static final String NAMED_QUERIES_LOCATION = "namedQueriesLocation"; + private static final String QUERY_LOOKUP_STRATEGY = "queryLookupStrategy"; + private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass"; + + private final AnnotationMetadata metadata; + private final AnnotationAttributes attributes; + + /** + * Creates a new {@link AnnotationRepositoryConfigurationSource} from the given {@link AnnotationMetadata} and + * annotation. + * + * @param metadata must not be {@literal null}. + * @param annotation must not be {@literal null}. + */ + public AnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class annotation) { + + Assert.notNull(metadata); + Assert.notNull(annotation); + + this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); + this.metadata = metadata; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages() + */ + public Iterable getBasePackages() { + + String[] value = attributes.getStringArray("value"); + String[] basePackages = attributes.getStringArray(BASE_PACKAGES); + Class[] basePackageClasses = attributes.getClassArray(BASE_PACKAGE_CLASSES); + + // Default configuration - return package of annotated class + if (value.length == 0 && basePackages.length == 0 && basePackageClasses.length == 0) { + String className = metadata.getClassName(); + return Collections.singleton(className.substring(0, className.lastIndexOf('.'))); + } + + Set packages = new HashSet(); + packages.addAll(Arrays.asList(value)); + packages.addAll(Arrays.asList(basePackages)); + + for (Class typeName : basePackageClasses) { + packages.add(ClassUtils.getPackageName(typeName)); + } + + return packages; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getQueryLookupStrategyKey() + */ + public Object getQueryLookupStrategyKey() { + return attributes.get(QUERY_LOOKUP_STRATEGY); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getNamedQueryLocation() + */ + public String getNamedQueryLocation() { + return getNullDefaultedAttribute(NAMED_QUERIES_LOCATION); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryImplementationPostfix() + */ + public String getRepositoryImplementationPostfix() { + return getNullDefaultedAttribute(REPOSITORY_IMPLEMENTATION_POSTFIX); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getSource() + */ + public Object getSource() { + return metadata; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getIncludeFilters() + */ + @Override + protected Iterable getIncludeFilters() { + return parseFilters("includeFilters"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters() + */ + @Override + protected Iterable getExcludeFilters() { + return parseFilters("excludeFilters"); + } + + private Set parseFilters(String attributeName) { + + Set result = new HashSet(); + AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName); + + for (AnnotationAttributes filter : filters) { + result.addAll(typeFiltersFor(filter)); + } + + return result; + } + + /** + * Returns the {@link String} attribute with the given name and defaults it to {@literal null} in case it's empty. + * + * @param attributeName + * @return + */ + private String getNullDefaultedAttribute(String attributeName) { + String attribute = attributes.getString(attributeName); + return StringUtils.hasText(attribute) ? attribute : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryFactoryBeanName() + */ + public String getRepositoryFactoryBeanName() { + return attributes.getClass(REPOSITORY_FACTORY_BEAN_CLASS).getName(); + } + + /** + * Returns the {@link AnnotationAttributes} of the annotation configured. + * + * @return the attributes will never be {@literal null}. + */ + public AnnotationAttributes getAttributes() { + return attributes; + } + + /** + * Copy of {@code ComponentScanAnnotationParser#typeFiltersFor}. + * + * @param filterAttributes + * @return + */ + private List typeFiltersFor(AnnotationAttributes filterAttributes) { + List typeFilters = new ArrayList(); + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : filterAttributes.getClassArray("value")) { + switch (filterType) { + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, "An error occured when processing a @ComponentScan " + + "ANNOTATION type filter: "); + @SuppressWarnings("unchecked") + Class annoClass = (Class) filterClass; + typeFilters.add(new AnnotationTypeFilter(annoClass)); + break; + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, "An error occured when processing a @ComponentScan " + + "CUSTOM type filter: "); + typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); + break; + default: + throw new IllegalArgumentException("unknown filter type " + filterType); + } + } + return typeFilters; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java deleted file mode 100644 index 2f2079887..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2008-2010 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.util.ClassUtils.*; -import static org.springframework.util.StringUtils.*; - -import org.springframework.util.Assert; - -/** - * A {@link SingleRepositoryConfigInformation} implementation that is not backed by an XML element but by a scanned - * interface. As this is derived from the parent, most of the lookup logic is delegated to the parent as well. - * - * @author Oliver Gierke - */ -public class AutomaticRepositoryConfigInformation extends - ParentDelegatingRepositoryConfigInformation { - - private final String interfaceName; - - /** - * Creates a new {@link AutomaticRepositoryConfigInformation} for the given interface name and - * {@link CommonRepositoryConfigInformation} parent. - * - * @param interfaceName - * @param parent - */ - public AutomaticRepositoryConfigInformation(String interfaceName, S parent) { - - super(parent); - Assert.notNull(interfaceName); - this.interfaceName = interfaceName; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.SingleRepositoryConfigInformation - * #getBeanId() - */ - public String getBeanId() { - - return uncapitalize(getShortName(interfaceName)); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.SingleRepositoryConfigInformation - * #getInterfaceName() - */ - public String getInterfaceName() { - - return interfaceName; - } -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java deleted file mode 100644 index 0312b2392..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2008-2010 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 org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.transaction.PlatformTransactionManager; -import org.w3c.dom.Element; - -/** - * Interface for shared repository information. - * - * @author Oliver Gierke - */ -public interface CommonRepositoryConfigInformation { - - /** - * Returns the element the repository information is derived from. - * - * @return - */ - Element getSource(); - - /** - * Returns the base package. - * - * @return - */ - String getBasePackage(); - - /** - * Returns the suffix to use for implementation bean lookup or class detection. - * - * @return - */ - String getRepositoryImplementationSuffix(); - - /** - * Returns the configured repository factory class. - * - * @return - */ - String getRepositoryFactoryBeanClassName(); - - /** - * Returns the bean name of the {@link PlatformTransactionManager} to be used. Returns {@literal null} if no reference - * has been configured explicitly. - * - * @return - */ - String getTransactionManagerRef(); - - /** - * Returns the strategy finder methods should be resolved. - * - * @return - */ - Key getQueryLookupStrategyKey(); - - /** - * Returns the location of the properties file to contain named queries. - * - * @return - */ - String getNamedQueriesLocation(); -} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java new file mode 100644 index 000000000..b27116905 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012 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 org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Default implementation of {@link RepositoryConfiguration}. + * + * @author Oliver Gierke + */ +public class DefaultRepositoryConfiguration implements + RepositoryConfiguration { + + private static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND; + private static final String DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX = "Impl"; + + private final T configurationSource; + private final String interfaceName; + + /** + * Creates a new {@link DefaultRepositoryConfiguration} from the given {@link RepositoryConfigurationSource} and + * interface name. + * + * @param configurationSource must not be {@literal null}. + * @param interfaceName must not be {@literal null} or empty. + */ + public DefaultRepositoryConfiguration(T configurationSource, String interfaceName) { + + Assert.notNull(configurationSource); + Assert.hasText(interfaceName); + + this.configurationSource = configurationSource; + this.interfaceName = interfaceName; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getBeanId() + */ + public String getBeanId() { + return StringUtils.uncapitalize(ClassUtils.getShortName(interfaceName)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getQueryLookupStrategyKey() + */ + public Object getQueryLookupStrategyKey() { + + Object configuredStrategy = configurationSource.getQueryLookupStrategyKey(); + return configuredStrategy != null ? configuredStrategy : DEFAULT_QUERY_LOOKUP_STRATEGY; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getBasePackages() + */ + public Iterable getBasePackages() { + return configurationSource.getBasePackages(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getRepositoryInterface() + */ + public String getRepositoryInterface() { + return interfaceName; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getConfigSource() + */ + public RepositoryConfigurationSource getConfigSource() { + return configurationSource; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getNamedQueryLocation() + */ + public String getNamedQueriesLocation() { + return configurationSource.getNamedQueryLocation(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getImplementationClassName() + */ + public String getImplementationClassName() { + return ClassUtils.getShortName(interfaceName) + getImplementationPostfix(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getImplementationBeanName() + */ + public String getImplementationBeanName() { + return StringUtils.uncapitalize(getImplementationClassName()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getImplementationPostfix() + */ + public String getImplementationPostfix() { + + String configuredPostfix = configurationSource.getRepositoryImplementationPostfix(); + return StringUtils.hasText(configuredPostfix) ? configuredPostfix : DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getSource() + */ + public Object getSource() { + return configurationSource.getSource(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getConfigurationSource() + */ + public T getConfigurationSource() { + return configurationSource; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getRepositoryFactoryBeanName() + */ + public String getRepositoryFactoryBeanName() { + return configurationSource.getRepositoryFactoryBeanName(); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java deleted file mode 100644 index 620916809..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2008-2010 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; - -/** - * @author Oliver Gierke - */ -public interface GlobalRepositoryConfigInformation> extends - CommonRepositoryConfigInformation { - - /** - * Returns the - * - * @param interfaceName - * @return - */ - T getAutoconfigRepositoryInformation(String interfaceName); - - /** - * Returns all {@link SingleRepositoryConfigInformation} instances used for manual configuration. - * - * @return - */ - Iterable getSingleRepositoryConfigInformations(); - - /** - * Returns whether to consider manual configuration. If this returns true, clients should use - * {@link #getSingleRepositoryConfigInformations()} to lookup configuration information for individual repository - * beans. - * - * @return - */ - boolean configureManually(); - - /** - * Returns the base interface to use - * - * @return - */ - Class getRepositoryBaseInterface(); -} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java deleted file mode 100644 index 50c220268..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2008-2010 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.util.StringUtils.*; - -import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.w3c.dom.Element; - -/** - * Configuration information for manual repository configuration. - * - * @author Oliver Gierke - */ -public class ManualRepositoryConfigInformation extends - ParentDelegatingRepositoryConfigInformation { - - private static final String CUSTOM_IMPL_REF = "custom-impl-ref"; - - private Element element; - - /** - * @param parent - */ - public ManualRepositoryConfigInformation(Element element, T parent) { - - super(parent); - this.element = element; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getBeanName() - */ - public String getBeanId() { - - return element.getAttribute("id"); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getInterfaceName() - */ - public String getInterfaceName() { - - return getBasePackage() + "." + capitalize(getBeanId()); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getCustomImplementationRef() - */ - @Override - public String getCustomImplementationRef() { - - return element.getAttribute(CUSTOM_IMPL_REF); - } - - /** - * Returns if a custom implementation shall be autodetected. - * - * @return - */ - @Override - public boolean autodetectCustomImplementation() { - - return !hasText(getCustomImplementationRef()); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.AbstractRepositoryInformation - * #getRepositoryImplementationSuffix() - */ - @Override - public String getRepositoryImplementationSuffix() { - - String value = element.getAttribute(RepositoryConfig.REPOSITORY_IMPL_POSTFIX); - return hasText(value) ? value : getParent().getRepositoryImplementationSuffix(); - } - - @Override - public String getTransactionManagerRef() { - - return getAttribute(RepositoryConfig.TRANSACTION_MANAGER_REF); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.CommonRepositoryInformation - * #getSource() - */ - @Override - public Element getSource() { - - return element; - } - - /** - * Returns the attribute of the current context. If it's not set the method will fall back to the parent's source. - * - * @param attribute - * @return - */ - protected String getAttribute(String attribute) { - - String value = getSource().getAttribute(attribute); - - if (hasText(value)) { - return value; - } - - value = getParent().getSource().getAttribute(attribute); - - return hasText(value) ? value : null; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.CommonRepositoryInformation - * #getRepositoryFactoryClassName() - */ - @Override - public String getRepositoryFactoryBeanClassName() { - - String value = element.getAttribute(RepositoryConfig.REPOSITORY_FACTORY_CLASS_NAME); - return hasText(value) ? value : getParent().getRepositoryFactoryBeanClassName(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.CommonRepositoryInformation - * #getQueryLookupStrategyKey() - */ - @Override - public Key getQueryLookupStrategyKey() { - - return Key.create(getAttribute(RepositoryConfig.QUERY_LOOKUP_STRATEGY)); - } -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java new file mode 100644 index 000000000..cae854c13 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.PropertiesFactoryBean; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Builder to create a {@link BeanDefinition} for a {@link NamedQueries} instance. + * + * @author Oliver Gierke + */ +public class NamedQueriesBeanDefinitionBuilder { + + private final String defaultLocation; + private String locations; + + /** + * Creates a new {@link NamedQueriesBeanDefinitionBuilder} using the given default location. + * + * @param defaultLocation must not be {@literal null} or empty. + */ + public NamedQueriesBeanDefinitionBuilder(String defaultLocation) { + + Assert.hasText(defaultLocation); + this.defaultLocation = defaultLocation; + } + + /** + * Sets the (comma-separated) locations to load the properties files from to back the {@link NamedQueries} instance. + * + * @param locations must not be {@literal null} or empty. + */ + public void setLocations(String locations) { + + Assert.hasText(locations); + this.locations = locations; + } + + /** + * Builds a new {@link BeanDefinition} from the given source. + * + * @param source + * @return + */ + public BeanDefinition build(Object source) { + + BeanDefinitionBuilder properties = BeanDefinitionBuilder.rootBeanDefinition(PropertiesFactoryBean.class); + + String locationsToUse = StringUtils.hasText(locations) ? locations : defaultLocation; + properties.addPropertyValue("locations", locationsToUse); + + if (!StringUtils.hasText(locations)) { + properties.addPropertyValue("ignoreResourceNotFound", true); + } + + AbstractBeanDefinition propertiesDefinition = properties.getBeanDefinition(); + propertiesDefinition.setSource(source); + + BeanDefinitionBuilder namedQueries = BeanDefinitionBuilder.rootBeanDefinition(PropertiesBasedNamedQueries.class); + namedQueries.addConstructorArgValue(propertiesDefinition); + + AbstractBeanDefinition namedQueriesDefinition = namedQueries.getBeanDefinition(); + namedQueriesDefinition.setSource(source); + + return namedQueriesDefinition; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java deleted file mode 100644 index 212beaa15..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2008-2010 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.util.StringUtils.*; - -import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.util.Assert; -import org.w3c.dom.Element; - -/** - * Base class for {@link SingleRepositoryConfigInformation} implementations. So these implementations will capture - * information for XML elements manually configuring a single repository bean. - * - * @author Oliver Gierke - */ -public abstract class ParentDelegatingRepositoryConfigInformation - implements SingleRepositoryConfigInformation { - - private final T parent; - - /** - * Creates a new {@link ParentDelegatingRepositoryConfigInformation} with the given - * {@link CommonRepositoryConfigInformation} as parent. - * - * @param parent - */ - public ParentDelegatingRepositoryConfigInformation(T parent) { - - Assert.notNull(parent); - this.parent = parent; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getParent() - */ - protected T getParent() { - - return parent; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.CommonRepositoryInformation - * #getBasePackage() - */ - public String getBasePackage() { - - return parent.getBasePackage(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getImplementationClassName() - */ - public String getImplementationClassName() { - - return capitalize(getBeanId()) + getRepositoryImplementationSuffix(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getImplementationBeanName() - */ - public String getImplementationBeanName() { - - return getBeanId() + getRepositoryImplementationSuffix(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * autodetectCustomImplementation() - */ - public boolean autodetectCustomImplementation() { - - return true; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getCustomImplementationRef() - */ - public String getCustomImplementationRef() { - - return getBeanId() + getRepositoryImplementationSuffix(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.CommonRepositoryInformation - * #getSource() - */ - public Element getSource() { - - return parent.getSource(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getRepositoryImplementationSuffix() - */ - public String getRepositoryImplementationSuffix() { - - return parent.getRepositoryImplementationSuffix(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getRepositoryFactoryBeanClassName() - */ - public String getRepositoryFactoryBeanClassName() { - - return parent.getRepositoryFactoryBeanClassName(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getTransactionManagerRef() - */ - public String getTransactionManagerRef() { - - return parent.getTransactionManagerRef(); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.RepositoryInformation# - * getQueryLookupStrategyKey() - */ - public Key getQueryLookupStrategyKey() { - - return parent.getQueryLookupStrategyKey(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.CommonRepositoryConfigInformation#getNamedQueriesLocation() - */ - public String getNamedQueriesLocation() { - return parent.getNamedQueriesLocation(); - } -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java new file mode 100644 index 000000000..f3d576e8a --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2012 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 java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Builder to create {@link BeanDefinitionBuilder} instance to eventually create Spring Data repository instances. + * + * @author Oliver Gierke + */ +public class RepositoryBeanDefinitionBuilder { + + private static final Log LOG = LogFactory.getLog(RepositoryBeanDefinitionBuilder.class); + + private final RepositoryConfiguration configuration; + private final RepositoryConfigurationExtension extension; + + /** + * Creates a new {@link RepositoryBeanDefinitionBuilder} from the given {@link RepositoryConfiguration} and + * {@link RepositoryConfigurationExtension}. + * + * @param configuration must not be {@literal null}. + * @param extension must not be {@literal null}. + */ + public RepositoryBeanDefinitionBuilder(RepositoryConfiguration configuration, + RepositoryConfigurationExtension extension) { + + Assert.notNull(configuration); + Assert.notNull(extension); + + this.configuration = configuration; + this.extension = extension; + } + + /** + * Builds a new {@link BeanDefinitionBuilder} from the given {@link BeanDefinitionRegistry} and {@link ResourceLoader} + * . + * + * @param registry must not be {@literal null}. + * @param resourceLoader must not be {@literal null}. + * @return + */ + public BeanDefinitionBuilder build(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) { + + Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); + Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); + + String factoryBeanName = configuration.getRepositoryFactoryBeanName(); + factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName : extension + .getRepositoryFactoryClassName(); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName); + + builder.addPropertyValue("repositoryInterface", configuration.getRepositoryInterface()); + builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey()); + + NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder( + extension.getDefaultNamedQueryLocation()); + + if (StringUtils.hasText(configuration.getNamedQueriesLocation())) { + definitionBuilder.setLocations(configuration.getNamedQueriesLocation()); + } + + builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource())); + + String customImplementationBeanName = registerCustomImplementation(registry, resourceLoader); + + if (customImplementationBeanName != null) { + builder.addPropertyReference("customImplementation", customImplementationBeanName); + } + + return builder; + } + + private String registerCustomImplementation(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) { + + String beanName = configuration.getImplementationBeanName(); + + // Already a bean configured? + if (registry.containsBeanDefinition(beanName)) { + return beanName; + } + + AbstractBeanDefinition beanDefinition = detectCustomImplementation(registry, resourceLoader); + + if (null == beanDefinition) { + return null; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering custom repository implementation: " + configuration.getImplementationBeanName() + " " + + beanDefinition.getBeanClassName()); + } + + beanDefinition.setSource(configuration.getSource()); + + registry.registerBeanDefinition(beanName, beanDefinition); + + return beanName; + } + + /** + * Tries to detect a custom implementation for a repository bean by classpath scanning. + * + * @param config + * @param parser + * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found + */ + private AbstractBeanDefinition detectCustomImplementation(BeanDefinitionRegistry registry, ResourceLoader loader) { + + // Build pattern to lookup implementation class + Pattern pattern = Pattern.compile(".*\\." + configuration.getImplementationClassName()); + + // Build classpath scanner and lookup bean definition + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + provider.setResourceLoader(loader); + provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); + + Set definitions = new HashSet(); + + for (String basePackage : configuration.getBasePackages()) { + definitions.addAll(provider.findCandidateComponents(basePackage)); + } + + if (definitions.isEmpty()) { + return null; + } + + if (definitions.size() == 1) { + return (AbstractBeanDefinition) definitions.iterator().next(); + } + + List implementationClassNames = new ArrayList(); + for (BeanDefinition bean : definitions) { + implementationClassNames.add(bean.getBeanClassName()); + } + + throw new IllegalStateException(String.format( + "Ambiguous custom implementations detected! Found %s but expected a single implementation!", + StringUtils.collectionToCommaDelimitedString(implementationClassNames))); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java new file mode 100644 index 000000000..4e5435b09 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionParser.java @@ -0,0 +1,132 @@ +/* + * Copyright 2008-2012 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.ReaderContext; +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.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.Assert; +import org.w3c.dom.Element; + +/** + * Base class to implement repository namespaces. These will typically consist of a main XML element potentially having + * child elements. The parser will wrap the XML element into a {@link GlobalRepositoryConfigInformation} object and + * allow either manual configuration or automatic detection of repository interfaces. + * + * @author Oliver Gierke + */ +public class RepositoryBeanDefinitionParser implements BeanDefinitionParser { + + private static final Log LOG = LogFactory.getLog(RepositoryBeanDefinitionParser.class); + + private final RepositoryConfigurationExtension extension; + + /** + * Creates a new {@link RepositoryBeanDefinitionParser} using the given {@link RepositoryConfigurationExtension}. + * + * @param extension must not be {@literal null}. + */ + public RepositoryBeanDefinitionParser(RepositoryConfigurationExtension extension) { + + Assert.notNull(extension); + this.extension = extension; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) + */ + public BeanDefinition parse(Element element, ParserContext parser) { + + try { + + XmlRepositoryConfigurationSource configSource = new XmlRepositoryConfigurationSource(element, parser); + + for (RepositoryConfiguration config : extension.getRepositoryConfigurations( + configSource, parser.getReaderContext().getResourceLoader())) { + registerGenericRepositoryFactoryBean(config, parser); + } + + extension.registerBeansForRoot(parser.getRegistry(), configSource); + + } catch (RuntimeException e) { + handleError(e, element, parser.getReaderContext()); + } + + return null; + } + + private void handleError(Exception e, Element source, ReaderContext reader) { + reader.error(e.getMessage(), reader.extractSource(source), e); + } + + /** + * Registers a generic repository factory bean for a bean with the given name and the provided configuration context. + * + * @param parser + * @param name + * @param context + */ + private void registerGenericRepositoryFactoryBean( + RepositoryConfiguration configuration, ParserContext parser) { + + RepositoryBeanDefinitionBuilder definitionBuilder = new RepositoryBeanDefinitionBuilder(configuration, extension); + + try { + + BeanDefinitionBuilder builder = definitionBuilder.build(parser.getRegistry(), parser.getReaderContext() + .getResourceLoader()); + + extension.postProcess(builder, configuration.getConfigurationSource()); + + AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); + beanDefinition.setSource(configuration.getSource()); + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering repository: " + configuration.getBeanId() + " - Interface: " + + configuration.getRepositoryInterface() + " - Factory: " + extension.getRepositoryFactoryClassName()); + } + + BeanComponentDefinition definition = new BeanComponentDefinition(beanDefinition, configuration.getBeanId()); + parser.registerBeanComponent(definition); + } catch (RuntimeException e) { + handleError(e, configuration.getConfigurationSource().getElement(), parser.getReaderContext()); + } + } + + /** + * 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 + */ + protected static boolean hasBean(Class type, BeanDefinitionRegistry registry) { + + String name = String.format("%s%s0", type.getName(), GENERATED_BEAN_NAME_SEPARATOR); + return registry.containsBeanDefinition(name); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java new file mode 100644 index 000000000..faa7a7536 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupport.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012 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 java.lang.annotation.Annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; + +/** + * Base class to implement {@link ImportBeanDefinitionRegistrar}s to enable repository + * + * @author Oliver Gierke + */ +public abstract class RepositoryBeanDefinitionRegistrarSupport implements ImportBeanDefinitionRegistrar { + + /* + * (non-Javadoc) + * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry) + */ + public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { + + Assert.notNull(annotationMetadata); + Assert.notNull(registry); + + ResourceLoader resourceLoader = new DefaultResourceLoader(); + AnnotationRepositoryConfigurationSource configuration = new AnnotationRepositoryConfigurationSource( + annotationMetadata, getAnnotation()); + + RepositoryConfigurationExtension extension = getExtension(); + extension.registerBeansForRoot(registry, configuration); + + for (RepositoryConfiguration repositoryConfiguration : extension + .getRepositoryConfigurations(configuration, resourceLoader)) { + + RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(repositoryConfiguration, extension); + BeanDefinitionBuilder definitionBuilder = builder.build(registry, resourceLoader); + + extension.postProcess(definitionBuilder, configuration); + + registry.registerBeanDefinition(repositoryConfiguration.getBeanId(), definitionBuilder.getBeanDefinition()); + } + } + + /** + * Return the annotation to obtain configuration information from. Will be wrappen into an + * {@link AnnotationRepositoryConfigurationSource} so have a look at the constants in there for what annotation + * attributes it expects. + * + * @return + */ + protected abstract Class getAnnotation(); + + /** + * Returns the {@link RepositoryConfigurationExtension} for store specific callbacks and {@link BeanDefinition} + * post-processing. + * + * @see RepositoryConfigurationExtensionSupport + * @return + */ + protected abstract RepositoryConfigurationExtension getExtension(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java new file mode 100644 index 000000000..124e08ba5 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java @@ -0,0 +1,231 @@ +package org.springframework.data.repository.config; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.RepositoryDefinition; +import org.springframework.data.repository.util.ClassUtils; +import org.springframework.util.Assert; + +/** + * Custom {@link ClassPathScanningCandidateComponentProvider} scanning for interfaces extending the given base + * interface. Skips interfaces annotated with {@link NoRepositoryBean}. + * + * @author Oliver Gierke + */ +class RepositoryComponentProvider extends ClassPathScanningCandidateComponentProvider { + + /** + * Creates a new {@link RepositoryComponentProvider} using the given {@link TypeFilter} to include components to be + * picked up. + * + * @param includeFilters the {@link TypeFilter}s to select repository interfaces to consider, must not be + * {@literal null}. + */ + public RepositoryComponentProvider(Iterable includeFilters) { + + super(false); + + Assert.notNull(includeFilters); + + if (includeFilters.iterator().hasNext()) { + for (TypeFilter filter : includeFilters) { + addIncludeFilter(filter); + } + } else { + super.addIncludeFilter(new InterfaceTypeFilter(Repository.class)); + super.addIncludeFilter(new AnnotationTypeFilter(RepositoryDefinition.class, true, true)); + } + + addExcludeFilter(new AnnotationTypeFilter(NoRepositoryBean.class)); + } + + /** + * Custom extension of {@link #addIncludeFilter(TypeFilter)} to extend the added {@link TypeFilter}. For the + * {@link TypeFilter} handed we'll have two filters registered: one additionally enforcing the + * {@link RepositoryDefinition} annotation, the other one forcing the extension of {@link Repository}. + * + * @see ClassPathScanningCandidateComponentProvider#addIncludeFilter(TypeFilter) + */ + @Override + public void addIncludeFilter(TypeFilter includeFilter) { + + List filterPlusInterface = new ArrayList(2); + filterPlusInterface.add(includeFilter); + filterPlusInterface.add(new InterfaceTypeFilter(Repository.class)); + + super.addIncludeFilter(new AllTypeFilter(filterPlusInterface)); + + List filterPlusAnnotation = new ArrayList(2); + filterPlusAnnotation.add(includeFilter); + filterPlusAnnotation.add(new AnnotationTypeFilter(RepositoryDefinition.class, true, true)); + + super.addIncludeFilter(new AllTypeFilter(filterPlusAnnotation)); + } + + /* + * (non-Javadoc) + * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition) + */ + @Override + protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { + + boolean isNonRepositoryInterface = !ClassUtils.isGenericRepositoryInterface(beanDefinition.getBeanClassName()); + boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass(); + + return isNonRepositoryInterface && isTopLevelType; + } + + /** + * {@link org.springframework.core.type.filter.TypeFilter} that only matches interfaces. Thus setting this up makes + * only sense providing an interface type as {@code targetType}. + * + * @author Oliver Gierke + */ + private static class InterfaceTypeFilter extends AssignableTypeFilter { + + /** + * Creates a new {@link InterfaceTypeFilter}. + * + * @param targetType + */ + public InterfaceTypeFilter(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 { + + return metadataReader.getClassMetadata().isInterface() && super.match(metadataReader, metadataReaderFactory); + } + } + + // Copy of Spring's AnnotationTypeFilter until SPR-8336 gets resolved. + + /** + * A simple filter which matches classes with a given annotation, checking inherited annotations as well. + *

+ * The matching logic mirrors that of Class.isAnnotationPresent(). + * + * @author Mark Fisher + * @author Ramnivas Laddad + * @author Juergen Hoeller + * @since 2.5 + */ + private static class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter { + + private final Class annotationType; + + private final boolean considerMetaAnnotations; + + /** + * Create a new AnnotationTypeFilter for the given annotation type. This filter will also match meta-annotations. To + * disable the meta-annotation matching, use the constructor that accepts a ' considerMetaAnnotations' + * argument. The filter will not match interfaces. + * + * @param annotationType the annotation type to match + */ + public AnnotationTypeFilter(Class annotationType) { + this(annotationType, true); + } + + /** + * Create a new AnnotationTypeFilter for the given annotation type. The filter will not match interfaces. + * + * @param annotationType the annotation type to match + * @param considerMetaAnnotations whether to also match on meta-annotations + */ + public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations) { + this(annotationType, considerMetaAnnotations, false); + } + + /** + * Create a new {@link AnnotationTypeFilter} for the given annotation type. + * + * @param annotationType the annotation type to match + * @param considerMetaAnnotations whether to also match on meta-annotations + * @param considerInterfaces whether to also match interfaces + */ + public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations, + boolean considerInterfaces) { + super(annotationType.isAnnotationPresent(Inherited.class), considerInterfaces); + this.annotationType = annotationType; + this.considerMetaAnnotations = considerMetaAnnotations; + } + + @Override + protected boolean matchSelf(MetadataReader metadataReader) { + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + return metadata.hasAnnotation(this.annotationType.getName()) + || (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); + } + + @Override + protected Boolean matchSuperClass(String superClassName) { + if (Object.class.getName().equals(superClassName)) { + return Boolean.FALSE; + } else if (superClassName.startsWith("java.")) { + try { + Class clazz = getClass().getClassLoader().loadClass(superClassName); + return (clazz.getAnnotation(this.annotationType) != null); + } catch (ClassNotFoundException ex) { + // Class not found - can't determine a match that way. + } + } + return null; + } + } + + /** + * Helper class to create a {@link TypeFilter} that matches if all the delegates match. + * + * @author Oliver Gierke + */ + private static class AllTypeFilter implements TypeFilter { + + private final List delegates; + + /** + * Creates a new {@link AllTypeFilter} to match if all the given delegates match. + * + * @param delegates must not be {@literal null}. + */ + public AllTypeFilter(List delegates) { + + Assert.notNull(delegates); + this.delegates = delegates; + } + + /* + * (non-Javadoc) + * @see org.springframework.core.type.filter.TypeFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory) + */ + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + + for (TypeFilter filter : delegates) { + if (!filter.match(metadataReader, metadataReaderFactory)) { + return false; + } + } + + return true; + } + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java deleted file mode 100644 index b18bcde17..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2008-2010 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 java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * Class defining access to the repository configuration abstracting the content of the {@code repositories} element in - * XML namespcae configuration. Defines default values to populate resulting repository beans with. - * - * @author Oliver Gierke - */ -public abstract class RepositoryConfig, S extends CommonRepositoryConfigInformation> - implements GlobalRepositoryConfigInformation { - - public static final String DEFAULT_REPOSITORY_IMPL_POSTFIX = "Impl"; - public static final String QUERY_LOOKUP_STRATEGY = "query-lookup-strategy"; - public static final String BASE_PACKAGE = "base-package"; - public static final String REPOSITORY_IMPL_POSTFIX = "repository-impl-postfix"; - public static final String REPOSITORY_FACTORY_CLASS_NAME = "factory-class"; - public static final String TRANSACTION_MANAGER_REF = "transaction-manager-ref"; - - private final Element element; - private final String defaultRepositoryFactoryBeanClassName; - - /** - * Creates an instance of {@code RepositoryConfig}. - * - * @param repositoriesElement - */ - protected RepositoryConfig(Element repositoriesElement, String defaultRepositoryFactoryBeanClassName) { - - Assert.notNull(repositoriesElement, "Element must not be null!"); - Assert.notNull(defaultRepositoryFactoryBeanClassName, - "Default repository factory bean class name must not be null!"); - - this.element = repositoriesElement; - this.defaultRepositoryFactoryBeanClassName = defaultRepositoryFactoryBeanClassName; - - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getSource() - */ - public Element getSource() { - - return element; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.GlobalRepositoryConfigInformation - * #configureManually() - */ - public boolean configureManually() { - - return getRepositoryElements().size() > 0; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getQueryLookupStrategyKey() - */ - public Key getQueryLookupStrategyKey() { - - String createFinderQueries = element.getAttribute(QUERY_LOOKUP_STRATEGY); - - return StringUtils.hasText(createFinderQueries) ? Key.create(createFinderQueries) : null; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getBasePackage() - */ - public String getBasePackage() { - - return element.getAttribute(BASE_PACKAGE); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getRepositoryFactoryClassName() - */ - public String getRepositoryFactoryBeanClassName() { - - String factoryClassName = getSource().getAttribute(REPOSITORY_FACTORY_CLASS_NAME); - return StringUtils.hasText(factoryClassName) ? factoryClassName : defaultRepositoryFactoryBeanClassName; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getRepositoryImplementationSuffix() - */ - public String getRepositoryImplementationSuffix() { - - String postfix = element.getAttribute(REPOSITORY_IMPL_POSTFIX); - return StringUtils.hasText(postfix) ? postfix : DEFAULT_REPOSITORY_IMPL_POSTFIX; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.CommonRepositoryConfigInformation - * #getTransactionManagerRef() - */ - public String getTransactionManagerRef() { - - String ref = element.getAttribute(TRANSACTION_MANAGER_REF); - return StringUtils.hasText(ref) ? ref : null; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.config.GlobalRepositoryInformation - * #getManualRepositoryInformations() - */ - public Iterable getSingleRepositoryConfigInformations() { - - Set infos = new HashSet(); - for (Element repositoryElement : getRepositoryElements()) { - infos.add(createSingleRepositoryConfigInformationFor(repositoryElement)); - } - - return infos; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.GlobalRepositoryConfigInformation#getRepositoryBaseInterface() - */ - public Class getRepositoryBaseInterface() { - return Repository.class; - } - - private Collection getRepositoryElements() { - - NodeList nodes = element.getChildNodes(); - Set result = new HashSet(); - - for (int i = 0; i < nodes.getLength(); i++) { - - Node node = nodes.item(i); - - boolean isElement = Node.ELEMENT_NODE == node.getNodeType(); - boolean isRepository = "repository".equals(node.getLocalName()); - - if (isElement && isRepository) { - result.add((Element) node); - } - } - - return result; - } - - /** - * Creates a {@link SingleRepositoryConfigInformation} for the given {@link Element}. - * - * @param element - * @return - */ - protected abstract T createSingleRepositoryConfigInformationFor(Element element); -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java new file mode 100644 index 000000000..d557201c8 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012 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 org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.data.repository.query.QueryLookupStrategy; + +/** + * Configuration information for a single repository instance. + * + * @author Oliver Gierke + */ +public interface RepositoryConfiguration { + + /** + * Returns the id of the {@link BeanDefinition} the repository shall be registered under. + * + * @return + */ + String getBeanId(); + + /** + * Returns the base packages that the repository was scanned under. + * + * @return + */ + Iterable getBasePackages(); + + /** + * Returns the interface name of the repository. + * + * @return + */ + String getRepositoryInterface(); + + /** + * Returns the key to resolve a {@link QueryLookupStrategy} from eventually. + * + * @see QueryLookupStrategy.Key + * @return + */ + Object getQueryLookupStrategyKey(); + + /** + * Returns the location of the file containing Spring Data named queries. + * + * @return + */ + String getNamedQueriesLocation(); + + /** + * Returns the class name of the custom implementation. + * + * @return + */ + String getImplementationClassName(); + + /** + * Returns the bean name of the custom implementation. + * + * @return + */ + String getImplementationBeanName(); + + /** + * Returns the name of the {@link FactoryBean} class to be used to create repository instances. + * + * @return + */ + String getRepositoryFactoryBeanName(); + + /** + * Returns the source of the {@link RepositoryConfiguration}. + * + * @return + */ + Object getSource(); + + /** + * Returns the {@link RepositoryConfigurationSource} that backs the {@link RepositoryConfiguration}. + * + * @return + */ + T getConfigurationSource(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java new file mode 100644 index 000000000..c0d1a8872 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012 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 java.util.Collection; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.io.ResourceLoader; + +/** + * SPI to implement store specific extension to the repository bean definition registration process. + * + * @see RepositoryConfigurationExtensionSupport + * @author Oliver Gierke + */ +public interface RepositoryConfigurationExtension { + + /** + * 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}. + * @return + */ + Collection> getRepositoryConfigurations( + T configSource, ResourceLoader loader); + + /** + * Returns the default location of the Spring Data named queries. + * + * @return must not be {@literal null} or empty. + */ + String getDefaultNamedQueryLocation(); + + /** + * Returns the name of the repository factory class to be used. + * + * @return + */ + String getRepositoryFactoryClassName(); + + /** + * Callback to register additional bean definitions for a {@literal repositories} root node. This usually includes + * beans you have to set up once independently of the number of repositories to be created. Will be called before any + * repositories bean definitions have been registered. + * + * @param registry + * @param source + */ + void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource); + + /** + * Callback to post process the {@link BeanDefinition} built from annotations and tweak the configuration if + * necessary. + * + * @param builder will never be {@literal null}. + * @param config will never be {@literal null}. + */ + void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config); + + /** + * Callback to post process the {@link BeanDefinition} built from XML and tweak the configuration if necessary. + * + * @param builder will never be {@literal null}. + * @param config will never be {@literal null}. + */ + void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java new file mode 100644 index 000000000..7111a8f8c --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012 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.util.Collection; +import java.util.HashSet; +import java.util.Set; + +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.io.ResourceLoader; +import org.springframework.util.Assert; + +/** + * 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 + */ +public abstract class RepositoryConfigurationExtensionSupport implements RepositoryConfigurationExtension { + + protected static final String REPOSITORY_INTERFACE_POST_PROCESSOR = "org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor"; + + /* + * (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) { + + Assert.notNull(configSource); + Assert.notNull(loader); + + Set> result = new HashSet>(); + + for (String candidate : configSource.getCandidates(loader)) { + result.add(getRepositoryConfiguration(candidate, configSource)); + } + 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) { + + AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(REPOSITORY_INTERFACE_POST_PROCESSOR) + .getBeanDefinition(); + + registerWithSourceAndGeneratedBeanName(registry, definition, configurationSource.getSource()); + } + + /** + * 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.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) { + + } + + /** + * Sets the given source on the given {@link AbstractBeanDefinition} and registers it inside the given + * {@link BeanDefinitionRegistry}. + * + * @param registry + * @param bean + * @param source + * @return + */ + public static String registerWithSourceAndGeneratedBeanName(BeanDefinitionRegistry registry, + AbstractBeanDefinition bean, Object source) { + + bean.setSource(source); + + String beanName = generateBeanName(bean, registry); + registry.registerBeanDefinition(beanName, bean); + + return beanName; + } + + /** + * 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 behaviour. + * + * @param interfaceName will never be {@literal null} or empty. + * @param configSource will never be {@literal null}. + * @return + */ + protected RepositoryConfiguration getRepositoryConfiguration( + String interfaceName, T configSource) { + return new DefaultRepositoryConfiguration(configSource, interfaceName); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java new file mode 100644 index 000000000..8446e8120 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012 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 java.util.Collection; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.io.ResourceLoader; +import org.springframework.data.repository.query.QueryLookupStrategy; + +/** + * Interface containing the configurable options for the Spring Data repository subsystem. + * + * @author Oliver Gierke + */ +public interface RepositoryConfigurationSource { + + /** + * Returns the actual source object that the configuration originated from. Will be used by the tooling to give visual + * feedback on where the repository instances actually come from. + * + * @return must not be {@literal null}. + */ + Object getSource(); + + /** + * Returns the base packages the repository interfaces shall be found under. + * + * @return must not be {@literal null}. + */ + Iterable getBasePackages(); + + /** + * Returns the {@link QueryLookupStrategy.Key} to define how query methods shall be resolved. + * + * @return + */ + Object getQueryLookupStrategyKey(); + + /** + * Returns the configured postfix to be used for looking up custom implementation classes. + * + * @return the postfix to use or {@literal null} in case none is configured. + */ + String getRepositoryImplementationPostfix(); + + /** + * @return + */ + String getNamedQueryLocation(); + + /** + * Returns the name of the class of the {@link FactoryBean} to actually create repository instances. + * + * @return + */ + String getRepositoryFactoryBeanName(); + + /** + * Returns the fully-qualified names of the repository interfaces to create repository instances for. + * + * @param loader + * @return + */ + Collection getCandidates(ResourceLoader loader); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java new file mode 100644 index 000000000..7eb0edb93 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012 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 java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.TypeFilter; + +/** + * Base class to implement {@link RepositoryConfigurationSource}s. + * + * @author Oliver Gierke + */ +public abstract class RepositoryConfigurationSourceSupport implements RepositoryConfigurationSource { + + protected static final String DEFAULT_REPOSITORY_IMPL_POSTFIX = "Impl"; + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getCandidates(org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider) + */ + public Collection getCandidates(ResourceLoader loader) { + + ClassPathScanningCandidateComponentProvider scanner = new RepositoryComponentProvider(getIncludeFilters()); + scanner.setResourceLoader(loader); + + for (TypeFilter filter : getExcludeFilters()) { + scanner.addExcludeFilter(filter); + } + + Set result = new HashSet(); + + for (String basePackage : getBasePackages()) { + Collection components = scanner.findCandidateComponents(basePackage); + for (BeanDefinition definition : components) { + result.add(definition.getBeanClassName()); + } + } + + return result; + } + + /** + * Return the {@link TypeFilter}s to define which types to exclude when scanning for repositories. Default + * implementation returns an empty collection. + * + * @return must not be {@literal null}. + */ + protected Iterable getExcludeFilters() { + return Collections.emptySet(); + } + + /** + * Return the {@link TypeFilter}s to define which types to include when scanning for repositories. Default + * implementation returns an empty collection. + * + * @return must not be {@literal null}. + */ + protected Iterable getIncludeFilters() { + return Collections.emptySet(); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java deleted file mode 100644 index 7bc13f859..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2008-2010 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; - -/** - * Interface to capture configuration information necessary to set up a single repository instance. - * - * @author Oliver Gierke - */ -public interface SingleRepositoryConfigInformation extends - CommonRepositoryConfigInformation { - - /** - * Returns the bean name to be used for the repository. - * - * @return - */ - String getBeanId(); - - /** - * Returns the name of the repository interface. - * - * @return - */ - String getInterfaceName(); - - /** - * Returns the class name of a possible custom repository implementation class to detect. - * - * @return - */ - String getImplementationClassName(); - - /** - * Returns the bean name a possibly found custom implementation shall be registered under. - * - * @return - */ - String getImplementationBeanName(); - - /** - * Returns the bean reference to the custom repository implementation. - * - * @return - */ - String getCustomImplementationRef(); - - /** - * Returns whether to try to autodetect a custom implementation. - * - * @return - */ - boolean autodetectCustomImplementation(); -} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java index 47da2f85d..9abaa7ade 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2010 the original author or authors. + * Copyright 2010-2012 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. @@ -16,17 +16,21 @@ package org.springframework.data.repository.config; import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashSet; import java.util.regex.Pattern; import org.springframework.beans.BeanUtils; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.parsing.ReaderContext; +import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AspectJTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.Assert; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -42,53 +46,55 @@ class TypeFilterParser { private static final String FILTER_TYPE_ATTRIBUTE = "type"; private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression"; - private final ClassLoader classLoader; private final ReaderContext readerContext; + private final ClassLoader classLoader; /** - * Creates a new {@link TypeFilterParser} with the given {@link ClassLoader} and {@link ReaderContext}. + * Creates a new {@link TypeFilterParser} with the given {@link ReaderContext}. * - * @param classLoader - * @param readerContext + * @param readerContext must not be {@literal null}. */ - public TypeFilterParser(ClassLoader classLoader, ReaderContext readerContext) { + public TypeFilterParser(XmlReaderContext readerContext) { + this(readerContext, readerContext.getResourceLoader().getClassLoader()); + } + + /** + * Constructor to ease testing as {@link XmlReaderContext#getBeanClassLoader()} is final and thus cannot be mocked + * easily. + * + * @param readerContext must not be {@literal null}. + * @param classLoader must not be {@literal null}. + */ + TypeFilterParser(ReaderContext readerContext, ClassLoader classLoader) { + + Assert.notNull(readerContext, "ReaderContext must not be null!"); + Assert.notNull(classLoader, "ClassLoader must not be null!"); - this.classLoader = classLoader; this.readerContext = readerContext; + this.classLoader = classLoader; } - /** - * Parses include and exclude filters form the given {@link Element}'s child elements and populates the given - * {@link ClassPathScanningCandidateComponentProvider} with the according {@link TypeFilter}s. - * - * @param element - * @param scanner - */ - public void parseFilters(Element element, ClassPathScanningCandidateComponentProvider scanner) { - - parseTypeFilters(element, scanner, Type.INCLUDE); - parseTypeFilters(element, scanner, Type.EXCLUDE); - } - - private void parseTypeFilters(Element element, ClassPathScanningCandidateComponentProvider scanner, Type type) { + public Iterable parseTypeFilters(Element element, Type type) { NodeList nodeList = element.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); + Collection filters = new HashSet(); + for (int i = 0; i < nodeList.getLength(); i++) { + + Node node = nodeList.item(i); Element childElement = type.getElement(node); if (childElement != null) { try { - - type.addFilter(createTypeFilter((Element) node, classLoader), scanner); - + filters.add(createTypeFilter(childElement, classLoader)); } catch (RuntimeException e) { readerContext.error(e.getMessage(), readerContext.extractSource(element), e.getCause()); } } } + + return filters; } protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader) { @@ -193,23 +199,9 @@ class TypeFilterParser { } } - private static enum Type { + static enum Type { - INCLUDE("include-filter") { - @Override - public void addFilter(TypeFilter filter, ClassPathScanningCandidateComponentProvider scanner) { - - scanner.addIncludeFilter(filter); - } - - }, - EXCLUDE("exclude-filter") { - @Override - public void addFilter(TypeFilter filter, ClassPathScanningCandidateComponentProvider scanner) { - - scanner.addExcludeFilter(filter); - } - }; + INCLUDE("include-filter"), EXCLUDE("exclude-filter"); private String elementName; @@ -236,7 +228,5 @@ class TypeFilterParser { return null; } - - abstract void addFilter(TypeFilter filter, ClassPathScanningCandidateComponentProvider scanner); } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java new file mode 100644 index 000000000..e3aba429c --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012 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 java.util.Arrays; + +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.repository.config.TypeFilterParser.Type; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * XML based {@link RepositoryConfigurationSource}. Uses configuration defined on {@link Element} attributes. + * + * @author Oliver Gierke + */ +public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSourceSupport { + + private static final String QUERY_LOOKUP_STRATEGY = "query-lookup-strategy"; + private static final String BASE_PACKAGE = "base-package"; + private static final String NAMED_QUERIES_LOCATION = "named-queries-location"; + private static final String REPOSITORY_IMPL_POSTFIX = "repository-impl-postfix"; + private static final String REPOSITORY_FACTORY_BEAN_CLASS_NAME = "factory-class"; + + private final Element element; + private final ParserContext context; + + private final Iterable includeFilters; + private final Iterable excludeFilters; + + /** + * Creates a new {@link XmlRepositoryConfigurationSource} using the given {@link Element} and {@link ParserContext}. + * + * @param element must not be {@literal null}. + * @param context must not be {@literal null}. + */ + public XmlRepositoryConfigurationSource(Element element, ParserContext context) { + + Assert.notNull(element); + Assert.notNull(context); + + this.element = element; + this.context = context; + + TypeFilterParser parser = new TypeFilterParser(context.getReaderContext()); + this.includeFilters = parser.parseTypeFilters(element, Type.INCLUDE); + this.excludeFilters = parser.parseTypeFilters(element, Type.EXCLUDE); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getSource() + */ + public Object getSource() { + return context.extractSource(element); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages() + */ + public Iterable getBasePackages() { + + String attribute = element.getAttribute(BASE_PACKAGE); + return Arrays.asList(StringUtils.delimitedListToStringArray(attribute, ",", " ")); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getQueryLookupStrategyKey() + */ + public Object getQueryLookupStrategyKey() { + return QueryLookupStrategy.Key.create(getNullDefaultedAttribute(element, QUERY_LOOKUP_STRATEGY)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getNamedQueryLocation() + */ + public String getNamedQueryLocation() { + return getNullDefaultedAttribute(element, NAMED_QUERIES_LOCATION); + } + + /** + * Returns the XML element backing the configuration. + * + * @return the element + */ + public Element getElement() { + return element; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters() + */ + @Override + protected Iterable getExcludeFilters() { + return excludeFilters; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getIncludeFilters() + */ + @Override + protected Iterable getIncludeFilters() { + return includeFilters; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryImplementationPostfix() + */ + public String getRepositoryImplementationPostfix() { + return getNullDefaultedAttribute(element, REPOSITORY_IMPL_POSTFIX); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryFactoryBeanName() + */ + public String getRepositoryFactoryBeanName() { + return getNullDefaultedAttribute(element, REPOSITORY_FACTORY_BEAN_CLASS_NAME); + } + + private String getNullDefaultedAttribute(Element element, String attributeName) { + String attribute = element.getAttribute(attributeName); + return StringUtils.hasText(attribute) ? attribute : null; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/package-info.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/package-info.java index 578944556..fc9cf00b4 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/package-info.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/package-info.java @@ -1,5 +1,5 @@ /** - * Base classes for repository namespace implementations. + * Support classes for repository namespace and JavaConfig integration. */ package org.springframework.data.repository.config; diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java index 35c86413d..b145ea027 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java @@ -58,4 +58,4 @@ public interface QueryLookupStrategy { * @return */ RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries); -} \ No newline at end of file +} diff --git a/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd index 785fb6d3a..56b3a6605 100644 --- a/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd +++ b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd @@ -43,21 +43,6 @@ - - - - - Declares a single DAO instance. - - - - - - - - - - diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java new file mode 100644 index 000000000..e112472fa --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012 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.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; + +/** + * Unit tests for {@link AnnotationRepositoryConfigurationSource}. + * + * @author Oliver Gierke + */ +public class AnnotationRepositoryConfigurationSourceUnitTests { + + RepositoryConfigurationSource source; + + @Before + public void setUp() { + + AnnotationMetadata annotationMetadata = new StandardAnnotationMetadata(SampleConfiguration.class, true); + source = new AnnotationRepositoryConfigurationSource(annotationMetadata, EnableRepositories.class); + } + + @Test + public void findsBasePackagesForClasses() { + + Iterable basePackages = source.getBasePackages(); + assertThat(basePackages, hasItem(AnnotationRepositoryConfigurationSourceUnitTests.class.getPackage().getName())); + } + + @Test + public void evaluatesExcludeFiltersCorrectly() { + + Collection candidates = source.getCandidates(new DefaultResourceLoader()); + assertThat(candidates, hasSize(1)); + assertThat(candidates, hasItem(MyRepository.class.getName())); + } + + @Test + public void defaultsToPackageOfAnnotatedClass() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultConfiguration.class); + RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata, + EnableRepositories.class); + + Iterable packages = source.getBasePackages(); + assertThat(packages, hasItem(DefaultConfiguration.class.getPackage().getName())); + } + + @Test + public void returnsConfiguredBasePackage() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultConfigurationWithBasePackage.class); + RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata, + EnableRepositories.class); + + Iterable packages = source.getBasePackages(); + assertThat(packages, hasItem("foo")); + } + + public static class Person { + + } + + @EnableRepositories + static class DefaultConfiguration { + + } + + @EnableRepositories(basePackages = "foo") + static class DefaultConfigurationWithBasePackage { + + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java new file mode 100644 index 000000000..3603508d3 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; + +/** + * Unit tests for {@link DefaultRepositoryConfiguration}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultRepositoryConfigurationUnitTests { + + @Mock + RepositoryConfigurationSource source; + + @Test + public void supportsBasicConfiguration() { + + RepositoryConfiguration configuration = new DefaultRepositoryConfiguration( + source, "com.acme.MyRepository"); + + assertThat(configuration.getBeanId(), is("myRepository")); + assertThat(configuration.getConfigurationSource(), is(source)); + assertThat(configuration.getImplementationBeanName(), is("myRepositoryImpl")); + assertThat(configuration.getImplementationClassName(), is("MyRepositoryImpl")); + assertThat(configuration.getRepositoryInterface(), is("com.acme.MyRepository")); + assertThat(configuration.getQueryLookupStrategyKey(), is((Object) Key.CREATE_IF_NOT_FOUND)); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/EnableRepositories.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/EnableRepositories.java new file mode 100644 index 000000000..f3a99e600 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/EnableRepositories.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 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 java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; + +@Retention(RetentionPolicy.RUNTIME) +public @interface EnableRepositories { + + String[] value() default {}; + + String[] basePackages() default {}; + + Class[] basePackageClasses() default {}; + + Filter[] includeFilters() default {}; + + Filter[] excludeFilters() default {}; + + Class repositoryFactoryBeanClass() default RepositoryFactoryBeanSupport.class; + + String namedQueriesLocation() default ""; + + String repositoryImplementationPostfix() default "Impl"; +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyOtherRepository.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyOtherRepository.java new file mode 100644 index 000000000..5a88c822a --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyOtherRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012 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 org.springframework.data.repository.Repository; +import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person; + +interface MyOtherRepository extends Repository { + +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyRepository.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyRepository.java new file mode 100644 index 000000000..368e2320c --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/MyRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012 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 org.springframework.data.repository.Repository; +import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person; + +interface MyRepository extends Repository { + +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java new file mode 100644 index 000000000..fb209cf89 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012 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.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.lang.annotation.Annotation; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; + +/** + * Integration test for {@link RepositoryBeanDefinitionRegistrarSupport}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class RepositoryBeanDefinitionRegistrarSupportIntegrationTests { + + @Mock + BeanDefinitionRegistry registry; + + @Test + public void registersBeanDefinitionForFoundBean() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(SampleConfiguration.class, true); + DummyRegistrar registrar = new DummyRegistrar(); + registrar.registerBeanDefinitions(metadata, registry); + + verify(registry, times(1)).registerBeanDefinition(eq("myRepository"), any(BeanDefinition.class)); + } + + private static class DummyRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation() + */ + @Override + protected Class getAnnotation() { + return EnableRepositories.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension() + */ + @Override + protected RepositoryConfigurationExtension getExtension() { + return new RepositoryConfigurationExtensionSupport() { + + public String getRepositoryFactoryClassName() { + return RepositoryFactoryBeanSupport.class.getName(); + } + + @Override + protected String getModulePrefix() { + return "commons"; + } + }; + } + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java index 9c3bd4b87..70be2565c 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2012 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. @@ -18,12 +18,15 @@ package org.springframework.data.repository.config; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.config.AbstractRepositoryConfigDefinitionParser.RepositoryComponentProvider; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; import org.springframework.data.repository.sample.SampleAnnotatedRepository; /** @@ -36,10 +39,22 @@ public class RepositoryComponentProviderUnitTests { @Test public void findsAnnotatedRepositoryInterface() { - RepositoryComponentProvider provider = new RepositoryComponentProvider(Repository.class); + RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections. emptyList()); Set components = provider.findCandidateComponents("org.springframework.data.repository.sample"); assertThat(components.size(), is(1)); assertThat(components.iterator().next().getBeanClassName(), is(SampleAnnotatedRepository.class.getName())); } + + @Test + public void limitsFoundRepositoriesToIncludeFiltersOnly() { + + List filters = Arrays.asList(new AssignableTypeFilter(MyOtherRepository.class)); + + RepositoryComponentProvider provider = new RepositoryComponentProvider(filters); + Set components = provider.findCandidateComponents("org.springframework.data.repository"); + + assertThat(components.size(), is(1)); + assertThat(components.iterator().next().getBeanClassName(), is(MyOtherRepository.class.getName())); + } } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/SampleConfiguration.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/SampleConfiguration.java new file mode 100644 index 000000000..9f30af2a9 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/SampleConfiguration.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012 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 org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; + +@EnableRepositories(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyOtherRepository.class), basePackageClasses = AnnotationRepositoryConfigurationSourceUnitTests.class) +class SampleConfiguration { + +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/TypeFilterParserUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/TypeFilterParserUnitTests.java index 426f4c624..c15200e97 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/TypeFilterParserUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/TypeFilterParserUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2010 the original author or authors. + * Copyright 2010-2012 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. @@ -15,13 +15,16 @@ */ package org.springframework.data.repository.config; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.io.IOException; + import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +35,8 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.repository.config.TypeFilterParser.Type; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -44,22 +49,24 @@ import org.xml.sax.SAXException; @RunWith(MockitoJUnitRunner.class) public class TypeFilterParserUnitTests { - private TypeFilterParser parser; - private Element documentElement; + static final Matcher> IS_ASSIGNABLE_TYPE_FILTER = hasItem(Matchers + .isA(AssignableTypeFilter.class)); + + TypeFilterParser parser; + Element documentElement; @Mock - private ClassLoader classLoader; + ReaderContext context; + @Mock + ClassLoader classLoader; @Mock - private ReaderContext context; - - @Mock - private ClassPathScanningCandidateComponentProvider scanner; + ClassPathScanningCandidateComponentProvider scanner; @Before public void setUp() throws SAXException, IOException, ParserConfigurationException { - parser = new TypeFilterParser(classLoader, context); + parser = new TypeFilterParser(context, classLoader); Resource sampleXmlFile = new ClassPathResource("type-filter-test.xml", TypeFilterParserUnitTests.class); @@ -74,9 +81,8 @@ public class TypeFilterParserUnitTests { Element element = DomUtils.getChildElementByTagName(documentElement, "firstSample"); - parser.parseFilters(element, scanner); - - verify(scanner, atLeastOnce()).addIncludeFilter(isA(AssignableTypeFilter.class)); + Iterable filters = parser.parseTypeFilters(element, Type.INCLUDE); + assertThat(filters, IS_ASSIGNABLE_TYPE_FILTER); } @Test @@ -84,8 +90,7 @@ public class TypeFilterParserUnitTests { Element element = DomUtils.getChildElementByTagName(documentElement, "secondSample"); - parser.parseFilters(element, scanner); - - verify(scanner, atLeastOnce()).addExcludeFilter(isA(AssignableTypeFilter.class)); + Iterable filters = parser.parseTypeFilters(element, Type.EXCLUDE); + assertThat(filters, IS_ASSIGNABLE_TYPE_FILTER); } } diff --git a/src/docbkx/repositories.xml b/src/docbkx/repositories.xml index 98a86ac5b..f046ea61e 100644 --- a/src/docbkx/repositories.xml +++ b/src/docbkx/repositories.xml @@ -433,7 +433,7 @@ List<User> findByLastname(String lastname, Pageable pageable);

- Spring + XML Configuration The easiest way to do so is by using the Spring namespace that is shipped with each Spring Data module that supports the repository @@ -501,19 +501,43 @@ List<User> findByLastname(String lastname, Pageable pageable); +
- - Manual configuration +
+ JavaConfig - If you'd rather like to manually define which repository - instances to create you can do this with nested <repository - /> elements. + The repository infrastructure can also be triggered using a + store-specific + @Enable${store}Repositories annotation + on a JavaConfig class. For an introduction into Java based + configuration of the Spring container please have a look at the + reference documentation. + JavaConfig in the Spring reference documentation - + - <repositories base-package="com.acme.repositories"> - <repository id="userRepository" /> -</repositories> - - + A sample configuration to enable Spring Data repositories would + look something like this. + + + Sample annotation based repository configuration + + @Configuration +@EnableJpaRepositories("com.acme.repositories") +class ApplicationConfiguration { + + @Bean + public EntityManagerFactory entityManagerFactory() { + // … + } +} + + + Note that the sample uses the JPA specific annotation which + would have to be exchanged dependingon which store module you actually + use. The same applies to the definition of the + EntityManagerFactory bean. Please + consult the sections covering the store-specific configuration.
diff --git a/src/docbkx/repository-namespace-reference.xml b/src/docbkx/repository-namespace-reference.xml index c0f747953..76e44198c 100644 --- a/src/docbkx/repository-namespace-reference.xml +++ b/src/docbkx/repository-namespace-reference.xml @@ -7,23 +7,21 @@
The <code><repositories /></code> element - The <repositories /> element acts as container - for <repository /> elements or can be left empty to - trigger auto detection + The <repositories /> triggers the setup of the + Spring Data repository infrastructure. The most important attribute is + base-package which defines the package to scan for Spring + Data repository interfaces. see - of repository instances. Attributes defined for - <repositories /> are propagated to contained - <repository /> elements but can be overridden of - course. + linkend="repositories.create-instances.spring"/> + Attributes - + - + @@ -41,9 +39,7 @@ interfaces extending *Repository (actual interface is determined by specific Spring Data module) in auto detection mode. All packages below the configured package - will be scanned, too. In auto configuration mode (no nested - <repository /> elements) wildcards are also - allowed. + will be scanned, too. Wildcards are also allowed. @@ -60,44 +56,8 @@ Determines the strategy to be used to create finder queries. See - for details. Defaults to create-if-not-found. - - - -
-
- -
- The <code><repository /></code> element - - The <repository /> element can contain all - attributes of <repositories /> except - base-package. This will result in overriding the values - configured in the surrounding <repositories /> element. - Thus here we will only document extended attributes. - - - Attributes - - - - - - - - - id - - Defines the id of the bean the repository instance will be - registered under as well as the repository interface name. - - - - custom-impl-ref - - Defines a reference to a custom repository implementation - bean. + linkend="repositories.query-methods.query-lookup-strategies"/> for + details. Defaults to create-if-not-found.