Java tutorial
/* * 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<S extends GlobalRepositoryConfigInformation<T>, T extends SingleRepositoryConfigInformation<S>> 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<String> repositoryInterfaces = getRepositoryInterfacesForAutoConfig(config, resourceLoader, parser.getReaderContext()); for (String repositoryInterface : repositoryInterfaces) { registerGenericRepositoryFactoryBean(parser, config.getAutoconfigRepositoryInformation(repositoryInterface)); } } private Set<String> 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<BeanDefinition> findCandidateComponents = scanner.findCandidateComponents(config.getBasePackage()); Set<String> interfaceNames = new HashSet<String>(); 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<BeanDefinition> definitions = provider.findCandidateComponents(config.getBasePackage()); if (definitions.size() == 0) { return null; } if (definitions.size() == 1) { return (AbstractBeanDefinition) definitions.iterator().next(); } List<String> implementationClassNames = new ArrayList<String>(); 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. * * <p> * The matching logic mirrors that of <code>Class.isAnnotationPresent()</code>. * * @author Mark Fisher * @author Ramnivas Laddad * @author Juergen Hoeller * @since 2.5 */ private static class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter { private final Class<? extends Annotation> 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 ' * <code>considerMetaAnnotations</code>' argument. The filter will not match interfaces. * * @param annotationType the annotation type to match */ public AnnotationTypeFilter(Class<? extends Annotation> 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<? extends Annotation> 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<? extends Annotation> 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; } } } }