package org.codehaus.mojo.dbupgrade.generic; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.mojo.dbupgrade.DBUpgradeException; import org.codehaus.mojo.dbupgrade.DBUpgradeLifecycle; import org.codehaus.mojo.dbupgrade.sqlexec.DefaultSQLExec; /* * Copyright 2000-2010 The Apache Software Foundation * * 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. */ /** * This class hooks up your global pre-upgrade, incremental upgrades, and finally global post-upgrade using both java and SQL * files through java resources. Each incremental upgrade has an associate version number to be stored in a configurable * database version table. DBUpgrade uses database version's value to pickup the next upgrade in your java resource, if any. * * Original source is from http://code.google.com/p/dbmigrate */ public class GenericDBUpgradeLifecycle implements DBUpgradeLifecycle { private final Log log = LogFactory.getLog( GenericDBUpgradeLifecycle.class ); private DefaultSQLExec sqlexec; private GenericDBUpgradeConfiguration config; public GenericDBUpgradeLifecycle( GenericDBUpgradeConfiguration config ) throws DBUpgradeException { this.config = config; this.sqlexec = new DefaultSQLExec( config ); this.initDBUpgrade(); } /** * Execute DB Upgrade lifecycle phases */ public int upgrade() throws DBUpgradeException { int upgradeCount = 0; try { runJavaUpgrader( "PreDBUpgrade" ); upgradeCount = this.incrementalUpgrade(); runJavaUpgrader( "PostDBUpgrade" ); } catch ( SQLException e ) { throw new DBUpgradeException( "Unable to upgrade", e ); } return upgradeCount; } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// private Connection getConnection() throws DBUpgradeException { try { return sqlexec.getConnection(); } catch ( SQLException e ) { throw new DBUpgradeException( e.getMessage(), e ); } } private void initDBUpgrade() throws DBUpgradeException { Statement stm = null; ResultSet rs = null; try { stm = sqlexec.getConnection().createStatement(); rs = stm.executeQuery( "SELECT " + config.getVersionColumnName() + " FROM " + config.getVersionTableName() ); if ( !rs.next() ) { this.createInitialVersion(); } } catch ( SQLException e ) { try { this.createVersionTable(); this.createInitialVersion(); } catch ( SQLException ex ) { throw new DBUpgradeException( "Unable to intialize version table.", ex ); } } finally { DbUtils.closeQuietly( rs ); DbUtils.closeQuietly( stm ); } } private void createVersionTable() throws SQLException { sqlexec.execute( "create table " + config.getVersionTableName() + " ( " + config.getVersionColumnName() + " integer )" ); } private void createInitialVersion() throws SQLException { sqlexec.execute( "insert into " + config.getVersionTableName() + " ( " + config.getVersionColumnName() + " ) values ( " + config.getInitialVersion() + " )" ); } private int incrementalUpgrade() throws DBUpgradeException { int latestVersion = this.getResourceVersion(); int upgradeCount = 0; while ( !internalUpgrade( latestVersion ) ) { upgradeCount++; } return upgradeCount; } private int getResourceVersion() throws DBUpgradeException { int version = 0; String packageName = config.getPackageName(); String versionResourcePath = packageName.replace( '.', '/' ) + "/" + config.getVersionResourceName(); InputStream versionResourceStream = this.getClass().getClassLoader().getResourceAsStream( versionResourcePath ); if ( versionResourceStream == null ) { throw new DBUpgradeException( "Could not find " + versionResourcePath + " resource in classpath" ); } try { Properties properties = new Properties(); properties.load( versionResourceStream ); version = Integer.parseInt( properties.getProperty( "version" ) ); } catch ( IOException e ) { throw new DBUpgradeException( "Could not read " + versionResourcePath + " resource in classpath", e ); } return version; } /** * get version for DB and check * * @param connection * @param latestVersion * @return * @throws DBUpgradeException */ private int getVersion( int latestVersion ) throws DBUpgradeException { Statement statement = null; int version; ResultSet rs = null; try { statement = sqlexec.getConnection().createStatement(); // lock the table? try { rs = statement.executeQuery( "SELECT distinct(" + config.getVersionColumnName() + ") FROM " + config.getVersionTableName() ); } catch ( SQLException e ) { //postgres requires this rollback sqlexec.rollback(); //version table is not available ,assume version 0 version = 0; return version; } if ( !rs.next() ) { // if no version present, assume version = 0 version = 0; } else { version = rs.getInt( 1 ); if ( rs.next() ) { throw new DBUpgradeException( "Multiple versions found in " + config.getVersionTableName() + " table" ); } if ( latestVersion < version ) { throw new DBUpgradeException( "Downgrade your database from " + version + " to " + latestVersion + " is not supported." ); } } } catch ( SQLException e ) { sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Could not execute version query", e ); } finally { DbUtils.closeQuietly( rs ); DbUtils.closeQuietly( statement ); } return version; } /** * Upgrade to the next version * * @param configuration * @return false when there are more upgrade to do * @throws DBUpgradeException */ private boolean internalUpgrade( int latestVersion ) throws DBUpgradeException { ResultSet rs = null; Statement statement = null; try { sqlexec.getConnection().setAutoCommit( false ); int version = getVersion( latestVersion ); int toVersion = version + 1; if ( version == latestVersion ) { //no more upgrade to do return true; } DBUpgrade upgrade = this.getUpgrader( version, toVersion ); try { log.info( "Database Upgrade: " + config.getDialect() + ":" + upgrade ); upgrade.upgradeDB( sqlexec, config.getDialect() ); } catch ( Exception e ) { log.error( e ); sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Failed to upgrade from version: " + version + " to " + toVersion, e ); } statement = this.getConnection().createStatement(); rs = statement.executeQuery( "SELECT distinct(" + config.getVersionColumnName() + ") FROM " + config.getVersionTableName() ); if ( !rs.next() ) { sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Unable to look up version info in database after upgrade" ); } else { int currentDBVersion = rs.getInt( 1 ); if ( currentDBVersion != toVersion ) { sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Version in database is: " + currentDBVersion + " which is not corrected incremented after upgrading from: " + currentDBVersion ); } } //all good sqlexec.commit(); } catch ( SQLException e ) { //more likely version table was not created during the first upgrade at version 0 sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Failed to upgrade due to database exception", e ); } finally { DbUtils.closeQuietly( rs ); DbUtils.closeQuietly( statement ); } return false; } private String versionToString( int version ) { String ret = ""; if ( version < 0 ) { ret = "_"; version = ( -1 ) * version; } return ret + version; } private DBUpgrade getJavaUpgrader( int fromVer, int toVer ) { String fromVersion = versionToString( fromVer ); String toVersion = versionToString( toVer ); String className = config.getPackageName() + "." + config.getUpgraderPrefix() + fromVersion + "to" + toVersion; DBUpgrade upgrader = null; upgrader = this.getJavaUpgrader( className ); if ( upgrader == null ) { String dialect = config.getDialect(); className = config.getPackageName() + "." + dialect + "." + config.getUpgraderPrefix() + fromVersion + "to" + toVersion; upgrader = this.getJavaUpgrader( className ); } return upgrader; } private DBUpgradeUsingSQL getSqlUpgrader( int fromVer, int toVer ) { DBUpgradeUsingSQL upgrader = null; String fromVersion = versionToString( fromVer ); String toVersion = versionToString( toVer ); String sqlResourceName = config.getPackageNameSlashFormat() + "/" + config.getUpgraderPrefix() + fromVersion + "to" + toVersion + ".sql"; upgrader = this.getSqlUpgrader( sqlResourceName ); if ( upgrader == null ) { sqlResourceName = config.getPackageNameSlashFormat() + "/" + config.getDialect() + "/" + config.getUpgraderPrefix() + fromVersion + "to" + toVersion + ".sql"; upgrader = this.getSqlUpgrader( sqlResourceName ); } return upgrader; } private DBUpgradeUsingSQL getSqlUpgrader( String resourceName ) { DBUpgradeUsingSQL upgrader = null; InputStream test = this.getClass().getClassLoader().getResourceAsStream( resourceName ); if ( test != null ) { try { test.close(); } catch ( IOException ioe ) { // whatever } upgrader = new DBUpgradeUsingSQL( resourceName ); } return upgrader; } /** * search of available upgrader * * @param version * @param toVersion * @param config * @param connection * @return * @throws DBUpgradeException */ private DBUpgrade getUpgrader( int version, int toVersion ) { DBUpgrade upgrader = null; upgrader = this.getJavaUpgrader( version, toVersion ); if ( upgrader == null ) { upgrader = this.getSqlUpgrader( version, toVersion ); } if ( upgrader == null ) { throw new RuntimeException( "Unable to find a DBUpgrader capable of upgrading from version " + version + " to version " + toVersion ); } return upgrader; } private void runJavaUpgrader( String upgraderName ) throws DBUpgradeException, SQLException { String className = null; className = config.getPackageName() + "." + upgraderName; runUpgrade( this.getJavaUpgrader( className ) ); className = config.getPackageName() + "." + config.getDialect() + "." + upgraderName; runUpgrade( this.getJavaUpgrader( className ) ); } private void runUpgrade( DBUpgrade upgrader ) throws DBUpgradeException { if ( upgrader != null ) { try { upgrader.upgradeDB( sqlexec, config.getDialect() ); sqlexec.commit(); } catch ( Exception e ) { sqlexec.rollbackQuietly(); throw new DBUpgradeException( "Unable to perform update: ", e ); } } } /** * return a DBUpgrade instance base on class name * * @param config * @param className * @return * @throws DBUpgradeException */ private DBUpgrade getJavaUpgrader( String className ) { DBUpgrade upgrader = null; Class<?> clazz = null; try { clazz = Class.forName( className ); } catch ( ClassNotFoundException e ) { //user does not supply the upgrader, return null so that the upgrade lifecycle can safely ignore it return null; } try { upgrader = (DBUpgrade) clazz.newInstance(); } catch ( Exception e ) { //all bets are off, user supplies the unexpected class type throw new RuntimeException( e ); } return upgrader; } }