com.brienwheeler.apps.schematool.SchemaToolBean.java Source code

Java tutorial

Introduction

Here is the source code for com.brienwheeler.apps.schematool.SchemaToolBean.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Brien L. Wheeler (brienwheeler@yahoo.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.brienwheeler.apps.schematool;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.persistence.spi.PersistenceUnitInfo;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.MappingException;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.cfg.Settings;
import org.hibernate.cfg.SettingsFactory;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.connection.ConnectionProviderFactory;
import org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;

import com.brienwheeler.lib.spring.beans.PropertyPlaceholderConfigurer;
import com.brienwheeler.lib.spring.beans.SmartClassPathXmlApplicationContext;

/**
 * A useful utility for dumping and/or executing database schema initialization or updates.
 * 
 * <p>By default, this tool reads the Spring beans file
 * <tt>classpath:com/brienwheeler/lib/db/appEntityManagerFactory.xml</tt>
 * looking for a bean named <tt>com.brienwheeler.lib.db.appEntityManagerFactory</tt>.
 * These values can be changed using the <tt>emfContextLocation</tt> and <tt>emfContextBeanName</tt>
 * properties.
 * 
 * <p>By default this tool only prints a set of SQL it believes is needed to update the
 * schema and performs no modifications agaisnt the database.  You may use the <tt>mode</tt>
 * and <tt>exec</tt> properties to change this behavior.  Setting Mode.CLEAN will instead generate
 * SQL that would initialize an empty database.
 * 
 * Setting exec true will cause the SQL to be executed against the database referred to by the
 * emfContextBean.  When mode == CLEAN and exec == true, database tables are dropped before being
 * re-instantiated.
 * 
 * @author Brien Wheeler
 */
public class SchemaToolBean implements InitializingBean, ApplicationListener<ContextRefreshedEvent> {
    private static final Log log = LogFactory.getLog(SchemaToolBean.class);

    public static enum Mode {
        UPDATE, CLEAN
    }

    private Mode mode = Mode.UPDATE;
    private boolean exec = false;
    private boolean closeContextOnDone = true;
    private String emfContextLocation = null;
    private String emfContextBeanName = null;
    private String emfPersistenceLocationsPropName = null;
    private String emfPersistenceLocationsPropValue = null;

    @Required
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    @Required
    public void setExec(boolean exec) {
        this.exec = exec;
    }

    @Required
    public void setCloseContextOnDone(boolean closeContextOnDone) {
        this.closeContextOnDone = closeContextOnDone;
    }

    @Required
    public void setEmfContextLocation(String emfContextLocation) {
        this.emfContextLocation = emfContextLocation;
    }

    @Required
    public void setEmfContextBeanName(String emfContextBeanName) {
        this.emfContextBeanName = emfContextBeanName;
    }

    @Required
    public void setEmfPersistenceLocationsPropName(String emfPersistenceLocationsPropName) {
        this.emfPersistenceLocationsPropName = emfPersistenceLocationsPropName;
    }

    @Required
    public void setEmfPersistenceLocationsPropValue(String emfPersistenceLocationsPropValue) {
        this.emfPersistenceLocationsPropValue = emfPersistenceLocationsPropValue;
    }

    public void afterPropertiesSet() throws Exception {
        try {
            // set based on default values in schematool properties file if not already set
            PropertyPlaceholderConfigurer.setProperty(emfPersistenceLocationsPropName,
                    emfPersistenceLocationsPropValue);

            Configuration configuration = determineHibernateConfiguration();

            Settings settings = null;
            if (exec || mode == Mode.UPDATE) {
                ClassPathXmlApplicationContext newContext = new SmartClassPathXmlApplicationContext(
                        emfContextLocation);
                try {
                    // get a reference to the factory bean, don't have it create a new EntityManager
                    LocalContainerEntityManagerFactoryBean factoryBean = newContext
                            .getBean("&" + emfContextBeanName, LocalContainerEntityManagerFactoryBean.class);
                    SettingsFactory settingsFactory = new InjectedDataSourceSettingsFactory(
                            factoryBean.getDataSource());
                    settings = settingsFactory.buildSettings(new Properties());
                } finally {
                    newContext.close();
                }
            }

            if (mode == Mode.UPDATE) {
                SchemaUpdate update = new SchemaUpdate(configuration, settings);
                update.execute(true, exec);
            } else {
                SchemaExport export = exec ? new SchemaExport(configuration, settings)
                        : new SchemaExport(configuration);
                export.create(true, exec);
            }
        } catch (Exception e) {
            log.error("Error running SchemaTool", e);
            throw e;
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (closeContextOnDone)
            ((AbstractApplicationContext) event.getApplicationContext()).close();
    }

    private Configuration determineHibernateConfiguration()
            throws MappingException, ClassNotFoundException, IOException {
        // Read in the bean definitions but don't go creating all the singletons,
        // because that causes creation of data sources and connections to database, etc.
        NonInitializingClassPathXmlApplicationContext context = new NonInitializingClassPathXmlApplicationContext(
                new String[] { emfContextLocation });
        try {
            context.loadBeanDefinitions();

            // Get well-known EntityManagerFactory bean definition by name
            BeanDefinition emfBeanDef = context.getBeanDefinition(emfContextBeanName);
            if (emfBeanDef == null) {
                throw new RuntimeException("no bean defined: " + emfContextBeanName);
            }

            // Get the name of the persistence unit for this EntityManagerFactory
            PropertyValue puNameProperty = emfBeanDef.getPropertyValues().getPropertyValue("persistenceUnitName");
            if (puNameProperty == null || !(puNameProperty.getValue() instanceof TypedStringValue)) {
                throw new RuntimeException(
                        "no property 'persistenceUnitName' defined on bean: " + emfContextBeanName);
            }
            String puName = ((TypedStringValue) puNameProperty.getValue()).getValue();

            // Get the name of the persistence unit for this EntityManagerFactory
            PropertyValue pumProperty = emfBeanDef.getPropertyValues().getPropertyValue("persistenceUnitManager");
            PersistenceUnitManager pum = null;
            if (pumProperty != null) {
                pum = createConfiguredPum(context, pumProperty);
            } else {
                pum = simulateDefaultPum(context, emfBeanDef);
            }

            // create the Hibernate configuration
            PersistenceUnitInfo pui = pum.obtainPersistenceUnitInfo(puName);
            Configuration configuration = new Configuration();
            configuration.setProperties(pui.getProperties());
            for (String className : pui.getManagedClassNames()) {
                configuration.addAnnotatedClass(Class.forName(className));
            }

            return configuration;
        } finally {
            context.close();
        }
    }

    private PersistenceUnitManager createConfiguredPum(NonInitializingClassPathXmlApplicationContext context,
            PropertyValue pumProperty) {
        if (pumProperty.getValue() instanceof BeanDefinitionHolder) {
            String beanName = ((BeanDefinitionHolder) pumProperty.getValue()).getBeanName();
            BeanDefinition beanDefinition = ((BeanDefinitionHolder) pumProperty.getValue()).getBeanDefinition();
            return (PersistenceUnitManager) context.createBean(beanName, beanDefinition);
        } else {
            throw new RuntimeException(
                    "property 'persistenceUnitManager' is not a BeanDefinition on bean: " + emfContextBeanName);
        }
    }

    @SuppressWarnings("unchecked")
    private PersistenceUnitManager simulateDefaultPum(NonInitializingClassPathXmlApplicationContext context,
            BeanDefinition emfBeanDef) {
        // Simulate Spring's use of DefaultPersistenceUnitManager
        DefaultPersistenceUnitManager defpum = new DefaultPersistenceUnitManager();

        // Set the location of the persistence XML -- when using the DPUM,
        // you can only set one persistence XML location on the EntityManagerFactory
        PropertyValue locationProperty = emfBeanDef.getPropertyValues().getPropertyValue("persistenceXmlLocation");
        if (locationProperty == null || !(locationProperty.getValue() instanceof TypedStringValue)) {
            throw new RuntimeException(
                    "no property 'persistenceXmlLocation' defined on bean: " + emfContextBeanName);
        }

        // Since PersistenceUnitPostProcessors may do things like set properties
        // onto the persistence unit, we need to instantiate them here so that
        // they get called when preparePersistenceUnitInfos() executes
        PropertyValue puiPostProcProperty = emfBeanDef.getPropertyValues()
                .getPropertyValue("persistenceUnitPostProcessors");
        if (puiPostProcProperty != null && puiPostProcProperty.getValue() instanceof ManagedList) {
            List<PersistenceUnitPostProcessor> postProcessors = new ArrayList<PersistenceUnitPostProcessor>();
            for (BeanDefinitionHolder postProcBeanDef : (ManagedList<BeanDefinitionHolder>) puiPostProcProperty
                    .getValue()) {
                String beanName = postProcBeanDef.getBeanName();
                BeanDefinition beanDefinition = postProcBeanDef.getBeanDefinition();
                PersistenceUnitPostProcessor postProcessor = (PersistenceUnitPostProcessor) context
                        .createBean(beanName, beanDefinition);
                postProcessors.add(postProcessor);
            }
            defpum.setPersistenceUnitPostProcessors(
                    postProcessors.toArray(new PersistenceUnitPostProcessor[postProcessors.size()]));
        }

        defpum.setPersistenceXmlLocation(((TypedStringValue) locationProperty.getValue()).getValue());
        defpum.preparePersistenceUnitInfos();

        return defpum;
    }

    /**
     * A context that provides a loadBeanDefinitions method that performs only
     * the portions of the traditional refresh() required to define all the beans
     * and substitute any placeholders. 
     */
    private static class NonInitializingClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
        private ConfigurableListableBeanFactory beanFactory;

        public NonInitializingClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
            super(configLocations, false, null);
        }

        public void loadBeanDefinitions() throws IOException {
            /*
             * This method mirrors the refresh() method up to and including
             * invoking the bean factory post processors, so that a complete set
             * of bean definitions with resolved placeholders exists.
             */

            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            //registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            //initMessageSource();

            // Initialize event multicaster for this context, otherwise close() throws IllegalStateException
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            //onRefresh();

            // Check for listener beans and register them.
            //registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            //finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event, otherwise close() throws IllegalStateException
            finishRefresh();
        }

        public BeanDefinition getBeanDefinition(String beanName) {
            return beanFactory.getBeanDefinition(beanName);
        }

        @Override
        protected DefaultListableBeanFactory createBeanFactory() {
            return new BeanDefinitionCreatingFactory(getInternalParentBeanFactory());
        }

        public Object createBean(String beanName, BeanDefinition beanDefinition) {
            return ((BeanDefinitionCreatingFactory) beanFactory).createBean(beanName, beanDefinition);
        }
    }

    /**
     * A BeanFactory that exposes a method that creates from the BeanDefinition 
     */
    private static class BeanDefinitionCreatingFactory extends DefaultListableBeanFactory {
        public BeanDefinitionCreatingFactory(BeanFactory parentBeanFactory) {
            super(parentBeanFactory);
        }

        public Object createBean(String beanName, BeanDefinition beanDefinition) {
            RootBeanDefinition rootBeanDefinition = getMergedBeanDefinition(beanName, beanDefinition);
            return createBean(beanName, rootBeanDefinition, null);
        }
    }

    /**
     * A helper class to create a Hibernate Settings object that uses an
     * InjectedDataSourceConnectionProvider.
     */
    private static class InjectedDataSourceSettingsFactory extends SettingsFactory {
        private static final long serialVersionUID = 1L;

        private DataSource dataSource;

        public InjectedDataSourceSettingsFactory(DataSource dataSource) {
            super();
            this.dataSource = dataSource;
        }

        @Override
        protected ConnectionProvider createConnectionProvider(Properties properties) {
            properties.setProperty(Environment.CONNECTION_PROVIDER,
                    InjectedDataSourceConnectionProvider.class.getCanonicalName());
            Map<String, Object> injectionData = new HashMap<String, Object>();
            injectionData.put("dataSource", dataSource);
            return ConnectionProviderFactory.newConnectionProvider(properties, injectionData);
        }
    }
}