/** * 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.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullWriter; import org.apache.commons.lang.StringUtils; import org.apereo.portal.hibernate.DelegatingHibernateIntegrator.HibernateConfiguration; import org.apereo.portal.hibernate.HibernateConfigurationAware; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.tool.hbm2ddl.FixedDatabaseMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.JdbcOperations; /** * Runs the Hibernate Schema Export tool using the specified DataSource for the target DB. * */ public class DataSourceSchemaExport implements ISchemaExport, HibernateConfigurationAware { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final Formatter formatter = FormatStyle.DDL.getFormatter(); private JdbcOperations jdbcOperations; private Configuration configuration; private Dialect dialect; private String persistenceUnit; /** The name of the persistence unit to use */ @Required public void setPersistenceUnit(String persistenceUnit) { this.persistenceUnit = persistenceUnit; } @Required public void setJdbcOperations(JdbcOperations jdbcOperations) { this.jdbcOperations = jdbcOperations; } @Override public boolean supports(String persistenceUnit) { return this.persistenceUnit.equals(persistenceUnit); } @Override public String getPersistenceUnitName() { return this.persistenceUnit; } @Override public void setConfiguration( String persistenceUnit, HibernateConfiguration hibernateConfiguration) { this.configuration = hibernateConfiguration.getConfiguration(); final SessionFactoryImplementor sessionFactory = hibernateConfiguration.getSessionFactory(); this.dialect = sessionFactory.getDialect(); } @Override public void drop(boolean export, String outputFile, boolean append) { final String[] dropSQL = configuration.generateDropSchemaScript(dialect); perform(dropSQL, export, outputFile, append, false); } @Override public void create(boolean export, String outputFile, boolean append) { final String[] createSQL = configuration.generateSchemaCreationScript(dialect); perform(createSQL, export, outputFile, append, true); } @Override public void update(boolean export, String outputFile, boolean append) { final String[] updateSQL = this.jdbcOperations.execute( new ConnectionCallback<String[]>() { @Override public String[] doInConnection(Connection con) throws SQLException, DataAccessException { final FixedDatabaseMetadata databaseMetadata = new FixedDatabaseMetadata(con, dialect); return configuration.generateSchemaUpdateScript( dialect, databaseMetadata); } }); perform(updateSQL, export, outputFile, append, true); } private void perform( String[] sqlCommands, boolean executeSql, String outputFile, boolean append, boolean failFast) { final PrintWriter sqlWriter = getSqlWriter(outputFile, append); try { for (final String sqlCommand : sqlCommands) { final String formatted = formatter.format(sqlCommand); sqlWriter.println(formatted); if (executeSql) { try { jdbcOperations.execute(sqlCommand); logger.info(sqlCommand); } catch (BadSqlGrammarException | UncategorizedSQLException e) { // For HSQL database and ant db-update to avoid failing when attempting to // delete a sequence that does not exist. // Needed until Hibernate 5. See https://hibernate.atlassian.net/browse/HHH-7002. if (sqlCommand.contains("drop constraint")) { logger.info( "Failed to execute (probably ignorable): {}, error message {}", sqlCommand, e.getMessage()); } else { handleSqlException(failFast, sqlCommand, e); } } catch (Exception e) { handleSqlException(failFast, sqlCommand, e); } } } } finally { IOUtils.closeQuietly(sqlWriter); } } private void handleSqlException(boolean failFast, String sqlCommand, Exception e) { if (failFast) { logger.error("Failed to execute: {}\n\t{}", sqlCommand, e.getMessage()); throw new RuntimeException("Failed to execute: " + sqlCommand, e); } else { logger.info( "Failed to execute (probably ignorable): {}, error message {}", sqlCommand, e.getMessage()); } } private PrintWriter getSqlWriter(String outputFile, boolean append) { final Writer sqlWriter; if (StringUtils.trimToNull(outputFile) != null) { try { // Insure any parent directories are created so we don't fail creating the SQL file. File file = new File(outputFile); if (!file.exists()) { file.getParentFile().mkdirs(); } sqlWriter = new BufferedWriter(new FileWriter(file, append)); } catch (IOException e) { throw new RuntimeException("", e); } } else { sqlWriter = new NullWriter(); } return new PrintWriter(sqlWriter); } }