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.