Java tutorial
/* * 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.synyx.hades.dao.config; import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*; import static org.synyx.hades.util.ClassUtils.*; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; import org.synyx.hades.dao.GenericDao; import org.synyx.hades.dao.NoDaoBean; import org.w3c.dom.Element; /** * Parser to create bean definitions for dao-config namespace. Registers bean * definitions for DAOs as well as * {@code PersistenceAnnotationBeanPostProcessor} and * {@code PersistenceExceptionTranslationPostProcessor} to transparently inject * entity manager factory instance and apply exception translation. * <p> * The definition parser allows two ways of configuration. Either it looks up * the manually defined DAO instances or scans the defined domain package for * candidates for DAOs. * * @author Oliver Gierke * @author Eberhard Wolff * @author Gil Markham */ class DaoConfigDefinitionParser implements BeanDefinitionParser { private static final Logger LOG = LoggerFactory.getLogger(DaoConfigDefinitionParser.class); private static final Class<?> PAB_POST_PROCESSOR = PersistenceAnnotationBeanPostProcessor.class; private static final Class<?> PET_POST_PROCESSOR = PersistenceExceptionTranslationPostProcessor.class; private static final String DAO_INTERFACE_POST_PROCESSOR = "org.synyx.hades.dao.orm.DaoInterfaceAwareBeanPostProcessor"; /* * (non-Javadoc) * * @see * org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org. * w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) */ public BeanDefinition parse(final Element element, final ParserContext parserContext) { try { DaoConfigContext configContext = new DaoConfigContext(element); configContext.validate(); if (configContext.configureManually()) { doManualConfiguration(configContext, parserContext); } else { doAutoConfiguration(configContext, parserContext); } } catch (IllegalArgumentException e) { parserContext.getReaderContext().error(e.getMessage(), parserContext.extractSource(element)); } Object beanSource = parserContext.extractSource(element); registerPostProcessors(parserContext.getRegistry(), beanSource); return null; } /** * Executes DAO auto configuration by scanning the provided entity package * for classes implementing {@code Persistable}. * * @param configContext * @param parserContext */ private void doAutoConfiguration(final DaoConfigContext configContext, final ParserContext parserContext) { LOG.debug("Triggering auto DAO detection"); ResourceLoader resourceLoader = parserContext.getReaderContext().getResourceLoader(); // Detect available DAO interfaces Set<String> daoInterfaces = getDaoInterfacesForAutoConfig(configContext, resourceLoader, parserContext.getReaderContext()); for (String daoInterface : daoInterfaces) { registerGenericDaoFactoryBean(parserContext, DaoContext.fromInterfaceName(daoInterface, configContext)); } } private Set<String> getDaoInterfacesForAutoConfig(final DaoConfigContext configContext, final ResourceLoader loader, final ReaderContext readerContext) { ClassPathScanningCandidateComponentProvider scanner = new GenericDaoComponentProvider(); scanner.setResourceLoader(loader); TypeFilterParser parser = new TypeFilterParser(loader.getClassLoader(), readerContext); parser.parseFilters(configContext.getElement(), scanner); Set<BeanDefinition> findCandidateComponents = scanner .findCandidateComponents(configContext.getDaoBasePackageName()); Set<String> interfaceNames = new HashSet<String>(); for (BeanDefinition definition : findCandidateComponents) { interfaceNames.add(definition.getBeanClassName()); } return interfaceNames; } /** * Proceeds manual configuration by traversing child elements. * * @param context * @param parserContext */ private void doManualConfiguration(final DaoConfigContext context, final ParserContext parserContext) { LOG.debug("Triggering manual DAO detection"); // Add dao declarations for (DaoContext daoContext : context.getDaoContexts()) { registerGenericDaoFactoryBean(parserContext, daoContext); } } /** * Registers a {@code GenericDaoFactoryBean} for a bean with the given name * and the provided configuration context. It is mainly used to construct * bean name, entity class name and DAO interface name. * * @param parserContext * @param name * @param context */ private void registerGenericDaoFactoryBean(final ParserContext parserContext, final DaoContext context) { Object beanSource = parserContext.extractSource(context.getElement()); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(context.getDaoFactoryClassName()); beanDefinitionBuilder.addPropertyValue("daoInterface", context.getInterfaceName()); beanDefinitionBuilder.addPropertyValue("queryLookupStrategy", context.getQueryLookupStrategy()); String entityManagerRef = context.getEntityManagerFactoryRef(); if (null != entityManagerRef) { beanDefinitionBuilder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(entityManagerRef, beanSource)); } beanDefinitionBuilder.addPropertyValue("transactionManager", context.getTransactionManagerRef()); String customImplementationBeanName = registerCustomImplementation(context, parserContext, beanSource); if (customImplementationBeanName != null) { beanDefinitionBuilder.addPropertyReference("customDaoImplementation", customImplementationBeanName); } AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); beanDefinition.setSource(beanSource); LOG.debug("Registering Hades DAO: {} - DAO interface: {} - Factory: {} - Custom implementation: {}", new Object[] { context.getBeanName(), context.getInterfaceName(), context.getDaoFactoryClassName(), customImplementationBeanName }); BeanComponentDefinition definition = new BeanComponentDefinition(beanDefinition, context.getBeanName()); parserContext.registerBeanComponent(definition); } /** * Creates an anonymous factory to extract the actual * {@link javax.persistence.EntityManager} from the * {@link javax.persistence.EntityManagerFactory} bean name reference. * * @param entityManagerFactoryBeanName * @param source * @return */ private BeanDefinition getEntityManagerBeanDefinitionFor(String entityManagerFactoryBeanName, Object source) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator"); builder.setFactoryMethod("createSharedEntityManager"); builder.addConstructorArgReference(entityManagerFactoryBeanName); AbstractBeanDefinition bean = builder.getRawBeanDefinition(); bean.setSource(source); return bean; } /** * Registers a possibly available custom DAO implementation on the DAO bean. * Tries to find an already registered bean to reference or tries to detect * a custom implementation itself. * * @param context * @param parserContext * @param source * @return the bean name of the custom implementation or {@code null} if * none available */ private String registerCustomImplementation(final DaoContext context, final ParserContext parserContext, final Object source) { String beanName = context.getImplementationBeanName(); // Already a bean configured? if (parserContext.getRegistry().containsBeanDefinition(beanName)) { return beanName; } // Autodetect implementation if (context.autodetectCustomImplementation()) { AbstractBeanDefinition beanDefinition = detectCustomImplementation(context, parserContext); if (null == beanDefinition) { return null; } LOG.debug("Registering custom DAO implementation: {} {}", context.getImplementationBeanName(), beanDefinition.getBeanClassName()); beanDefinition.setSource(source); parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName)); } else { beanName = context.getCustomImplementationRef(); } return beanName; } /** * Tries to detect a custom implementation for a DAO bean by classpath * scanning. * * @param context * @param parser * @return the {@code BeanDefinition} of the custom implementation or null * if none found */ private AbstractBeanDefinition detectCustomImplementation(final DaoContext context, final ParserContext parser) { // Build pattern to lookup implementation class Pattern pattern = Pattern.compile(".*" + context.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(context.getDaoBasePackageName()); return (0 == definitions.size() ? null : (AbstractBeanDefinition) definitions.iterator().next()); } /** * Registers necessary (Bean)PostProcessor instances if they have not * already been registered. * * @param registry * @param source */ private void registerPostProcessors(final BeanDefinitionRegistry registry, final Object source) { // Create PersistenceAnnotationPostProcessor definition if (!hasBean(PAB_POST_PROCESSOR, registry)) { AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(PAB_POST_PROCESSOR) .getBeanDefinition(); registerWithSourceAndGeneratedBeanName(registry, definition, source); } // Create PersistenceExceptionTranslationPostProcessor definition if (!hasBean(PET_POST_PROCESSOR, registry)) { AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(PET_POST_PROCESSOR) .getBeanDefinition(); registerWithSourceAndGeneratedBeanName(registry, definition, source); } AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(DAO_INTERFACE_POST_PROCESSOR) .getBeanDefinition(); registerWithSourceAndGeneratedBeanName(registry, definition, source); } private static boolean hasBean(Class<?> clazz, BeanDefinitionRegistry registry) { String name = String.format("%s%s0", clazz.getName(), GENERATED_BEAN_NAME_SEPARATOR); return registry.containsBeanDefinition(name); } private 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 {@link GenericDao}. Skips interfaces annotated with * {@link NoDaoBean}. * * @author Oliver Gierke */ static class GenericDaoComponentProvider extends ClassPathScanningCandidateComponentProvider { /** * Creates a new {@link GenericDaoComponentProvider}. */ public GenericDaoComponentProvider() { super(false); addIncludeFilter(new InterfaceTypeFilter(GenericDao.class)); addExcludeFilter(new AnnotationTypeFilter(NoDaoBean.class)); } /* * (non-Javadoc) * * @seeorg.springframework.context.annotation. * ClassPathScanningCandidateComponentProvider * #isCandidateComponent(org.springframework * .beans.factory.annotation.AnnotatedBeanDefinition) */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { boolean isNonHadesInterfaces = !isHadesDaoInterface(beanDefinition.getBeanClassName()); boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass(); return isNonHadesInterfaces && 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); } } } }