/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.tools.dbloader; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.hibernate.DelegatingHibernateIntegrator.HibernateConfiguration; import org.apereo.portal.hibernate.HibernateConfigurationAware; import org.apereo.portal.jpa.BasePortalJpaDao; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.mapping.ForeignKey; import org.hibernate.mapping.Index; import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Lazy; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.dao.DataAccessException; import org.springframework.dao.NonTransientDataAccessResourceException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionOperations; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Can drop and/or create tables based on an XML table definition file and populate tables based on * a XML data definition file. Table creation is done using the Hibernate mapping APIs to allow for * a full range of database support. * */ @Component("dbLoader") @Lazy @DependsOn({ BasePortalJpaDao.PERSISTENCE_UNIT_NAME + "EntityManagerFactory", "hibernateConfigurationAwareInjector" }) public class HibernateDbLoader implements IDbLoader, ResourceLoaderAware, HibernateConfigurationAware { protected final Log logger = LogFactory.getLog(this.getClass()); private Configuration configuration; private Dialect dialect; private JdbcOperations jdbcOperations; private TransactionOperations transactionOperations; private ResourceLoader resourceLoader; @Autowired public void setJdbcOperations( @Qualifier(BasePortalJpaDao.PERSISTENCE_UNIT_NAME) JdbcOperations jdbcOperations) { this.jdbcOperations = jdbcOperations; } @Autowired public void setTransactionOperations( @Qualifier(BasePortalJpaDao.PERSISTENCE_UNIT_NAME) TransactionOperations transactionOperations) { this.transactionOperations = transactionOperations; } @Override public boolean supports(String persistenceUnit) { return BasePortalJpaDao.PERSISTENCE_UNIT_NAME.equals(persistenceUnit); } @Override public void setConfiguration( String persistenceUnit, HibernateConfiguration hibernateConfiguration) { final SessionFactoryImplementor sessionFactory = hibernateConfiguration.getSessionFactory(); this.dialect = sessionFactory.getDialect(); this.configuration = hibernateConfiguration.getConfiguration(); } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void process(DbLoaderConfig configuration) throws ParserConfigurationException, SAXException, IOException { final String scriptFile = configuration.getScriptFile(); final List<String> script; if (scriptFile == null) { script = null; } else { script = new LinkedList<String>(); } final ITableDataProvider tableData = this.loadTables(configuration, dialect); //Handle table drop/create if (configuration.isDropTables() || configuration.isCreateTables()) { //Load Table object model final Map<String, Table> tables = tableData.getTables(); final Mapping mapping = this.configuration.buildMapping(); final String defaultCatalog = this.configuration.getProperty(Environment.DEFAULT_CATALOG); final String defaultSchema = this.configuration.getProperty(Environment.DEFAULT_SCHEMA); final Map<String, DataAccessException> failedSql = new LinkedHashMap<String, DataAccessException>(); //Generate and execute drop table scripts if (configuration.isDropTables()) { final List<String> dropScript = this.dropScript(tables.values(), dialect, defaultCatalog, defaultSchema); if (script == null) { this.logger.info("Dropping existing tables"); for (final String sql : dropScript) { this.logger.info(sql); try { jdbcOperations.update(sql); } catch (NonTransientDataAccessResourceException dae) { throw dae; } catch (DataAccessException dae) { failedSql.put(sql, dae); } } } else { script.addAll(dropScript); } } //Log any drop/create statements that failed for (final Map.Entry<String, DataAccessException> failedSqlEntry : failedSql.entrySet()) { this.logger.warn( "'" + failedSqlEntry.getKey() + "' failed to execute due to " + failedSqlEntry.getValue()); } //Generate and execute create table scripts if (configuration.isCreateTables()) { final List<String> createScript = this.createScript( tables.values(), dialect, mapping, defaultCatalog, defaultSchema); if (script == null) { this.logger.info("Creating tables"); for (final String sql : createScript) { this.logger.info(sql); jdbcOperations.update(sql); } } else { script.addAll(createScript); } } } //Perform database population if (script == null && configuration.isPopulateTables()) { this.logger.info("Populating database"); final Map<String, Map<String, Integer>> tableColumnTypes = tableData.getTableColumnTypes(); this.populateTables(configuration, tableColumnTypes); } //Write out the script file if (script != null) { for (final ListIterator<String> iterator = script.listIterator(); iterator.hasNext(); ) { final String sql = iterator.next(); iterator.set(sql + ";"); } final File outputFile = new File(scriptFile); FileUtils.writeLines(outputFile, script); this.logger.info("Saved DDL to: " + outputFile.getAbsolutePath()); } } protected ITableDataProvider loadTables(DbLoaderConfig configuration, Dialect dialect) throws ParserConfigurationException, SAXException, IOException { //Locate tables.xml final String tablesFileName = configuration.getTablesFile(); final Resource tablesFile = this.resourceLoader.getResource(tablesFileName); if (!tablesFile.exists()) { throw new IllegalArgumentException("Could not find tables file: " + tablesFile); } //Setup parser with custom handler to generate Table model and parse final SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); final TableXmlHandler dh = new TableXmlHandler(dialect); saxParser.parse(new InputSource(tablesFile.getInputStream()), dh); return dh; } /** Generate the drop scripts and add them to the script list */ @SuppressWarnings("unchecked") protected List<String> dropScript( Collection<Table> tables, Dialect dialect, String defaultCatalog, String defaultSchema) { final List<String> script = new ArrayList<String>(tables.size() * 2); if (dialect.dropConstraints()) { for (final Table table : tables) { if (table.isPhysicalTable()) { for (final Iterator<ForeignKey> subIter = table.getForeignKeyIterator(); subIter.hasNext(); ) { final ForeignKey fk = subIter.next(); if (fk.isPhysicalConstraint()) { script.add(fk.sqlDropString(dialect, defaultCatalog, defaultSchema)); } } } } } for (final Table table : tables) { if (table.isPhysicalTable()) { script.add(table.sqlDropString(dialect, defaultCatalog, defaultSchema)); } } return script; } /** Generate create scripts and add them to the script list */ @SuppressWarnings("unchecked") protected List<String> createScript( Collection<Table> tables, Dialect dialect, Mapping mapping, String defaultCatalog, String defaultSchema) { final List<String> script = new ArrayList<String>(tables.size() * 2); for (final Table table : tables) { if (table.isPhysicalTable()) { script.add(table.sqlCreateString(dialect, mapping, defaultCatalog, defaultSchema)); } } for (final Table table : tables) { if (table.isPhysicalTable()) { if (!dialect.supportsUniqueConstraintInCreateAlterTable()) { for (final Iterator<UniqueKey> subIter = table.getUniqueKeyIterator(); subIter.hasNext(); ) { final UniqueKey uk = subIter.next(); final String constraintString = uk.sqlCreateString(dialect, mapping, defaultCatalog, defaultSchema); if (constraintString != null) { script.add(constraintString); } } } for (final Iterator<Index> subIter = table.getIndexIterator(); subIter.hasNext(); ) { final Index index = subIter.next(); script.add( index.sqlCreateString(dialect, mapping, defaultCatalog, defaultSchema)); } if (dialect.hasAlterTable()) { for (final Iterator<ForeignKey> subIter = table.getForeignKeyIterator(); subIter.hasNext(); ) { final ForeignKey fk = subIter.next(); if (fk.isPhysicalConstraint()) { script.add( fk.sqlCreateString( dialect, mapping, defaultCatalog, defaultSchema)); } } } } } return script; } protected void populateTables( DbLoaderConfig configuration, Map<String, Map<String, Integer>> tableColumnTypes) throws ParserConfigurationException, SAXException, IOException { //Locate tables.xml final String dataFileName = configuration.getDataFile(); final Resource dataFile = this.resourceLoader.getResource(dataFileName); if (!dataFile.exists()) { throw new IllegalArgumentException("Could not find data file: " + dataFile); } //Setup parser with custom handler to generate Table model and parse final SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); final DataXmlHandler dh = new DataXmlHandler(jdbcOperations, transactionOperations, tableColumnTypes); saxParser.parse(new InputSource(dataFile.getInputStream()), dh); } }