org.synyx.hades.dao.config.DaoConfigDefinitionParser.java Source code

Java tutorial

Introduction

Here is the source code for org.synyx.hades.dao.config.DaoConfigDefinitionParser.java

Source

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