/* * RHQ Management Platform * Copyright (C) 2012 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.URL; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Map; import java.util.Properties; import org.apache.tools.ant.Project; import org.apache.tools.ant.helper.ProjectHelper2; import org.rhq.core.db.setup.DBSetup; import org.rhq.enterprise.server.test.AbstractEJB3Test; import liquibase.Liquibase; import liquibase.database.Database; import liquibase.database.DatabaseFactory; import liquibase.exception.LiquibaseException; import liquibase.integration.commandline.CommandLineUtils; import liquibase.resource.ClassLoaderResourceAccessor; /** * Provides utility methods for doing dbresets, dbsetups, and dbupgrades. * * @author Ian Springer */ public class DbSetupUtility { public static final String JON300_SCHEMA_VERSION = "2.116"; private static final String BASE_RESOURCE_PATH = DbSetupUtility.class.getPackage().getName().replace('.', '/'); private static TestDatasourceConfiguration testDsConfig; private static TestDatasourceConfiguration getTestDatasourceConfiguration() { if (testDsConfig == null) { Properties testDsProperties = new Properties(); InputStream resourceAsStream = DbSetupUtility.class.getResourceAsStream("test-ds.properties"); try { testDsProperties.load(resourceAsStream); } catch (IOException e) { throw new RuntimeException("Failed to load test-ds.properties from classloader.", e); } testDsConfig = new TestDatasourceConfiguration(testDsProperties); System.out.println("Using test datasource with config " + testDsConfig + "..."); } return testDsConfig; } public static void dbreset() throws Exception { TestDatasourceConfiguration testDs = getTestDatasourceConfiguration(); System.out.println("Resetting DB at " + testDs.connectionUrl + "..."); // NOTE: We do not use DBReset.performDBReset() here, since DBReset deletes the schema, which requires there to // be no active connections to the DB. Liquibase.dropAll(), on the other hand, just deletes all the // objects in the DB, which has no such requirement. String dbDriver = DatabaseFactory.getInstance().findDefaultDriver(testDs.connectionUrl); Database database = CommandLineUtils.createDatabaseObject(DbSetupUtility.class.getClassLoader(), testDs.connectionUrl, testDs.userName, testDs.password, dbDriver, null, null, null); //Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation( // new JdbcConnection(AbstractEJB3Test.getConnection())); Liquibase liquibase = new Liquibase(null, new ClassLoaderResourceAccessor(), database); liquibase.dropAll(); dropRhqCalltimeDataKeyTable(database); } public static void dbsetup() throws Exception { dbsetup(null); } public static void dbsetup(String jonVersion) throws Exception { String schemaFileResourcePath; String dataFileResourcePath; if (jonVersion != null) { System.out.println("Installing new RHQ DB with schema from JON version [" + jonVersion + "]..."); schemaFileResourcePath = BASE_RESOURCE_PATH + "/" + "db-schema-combined-" + jonVersion + ".xml"; dataFileResourcePath = BASE_RESOURCE_PATH + "/" + "db-data-combined-" + jonVersion + ".xml"; } else { System.out.println("Installing new RHQ DB with latest schema version..."); schemaFileResourcePath = "db-schema-combined.xml"; dataFileResourcePath = "db-data-combined.xml"; } TestDatasourceConfiguration testDs = getTestDatasourceConfiguration(); DBSetup dbsetup = new DBSetup(testDs.connectionUrl, testDs.userName, testDs.password); dbsetup.setup(schemaFileResourcePath); dbsetup.setup(dataFileResourcePath); } public static void dbupgrade(String targetSchemaVersion) throws Exception { System.out.println("Upgrading RHQ DB to schema version [" + targetSchemaVersion + "]..."); File logfile = File.createTempFile("rhq.test", "dbupgrade.log"); TestDatasourceConfiguration testDs = getTestDatasourceConfiguration(); try { // Get the URL for the dbupgrade Ant script, which is located in the dbutils jar. URL dbupgradeXmlFileUrl = DbSetupUtility.class.getClassLoader().getResource("db-upgrade.xml"); Properties antProps = new Properties(); antProps.setProperty("jdbc.url", testDs.connectionUrl); antProps.setProperty("jdbc.user", testDs.userName); antProps.setProperty("jdbc.password", testDs.password); antProps.setProperty("target.schema.version", targetSchemaVersion); startAnt(dbupgradeXmlFileUrl, "db-ant-tasks.properties", antProps, logfile); } catch (Exception e) { throw new RuntimeException("Cannot upgrade the RHQ DB at [" + testDs.connectionUrl + "] to schema version [" + targetSchemaVersion + "].", e); } return; } private static void dropRhqCalltimeDataKeyTable(Database database) throws LiquibaseException { // For some reason Liquibase always fails to drop the rhq_calltime_data_key table, logging the following: // // WARNING 2/22/12 4:56 PM:liquibase: Foreign key rhq_calltime_data_value_key_id_fkey references table // rhq_calltime_data_key, which is in a different schema. Retaining FK in // diff, but table will not be diffed. // // The workaround is to manually drop the table via JDBC once Liquibase has cleared out everything else. System.out.println("Dropping rhq_calltime_data_key table..."); // NOTE: The below attempt to delete the table individually via Liquibase doesn't even work... /*SqlVisitor sqlVisitor = new AbstractSqlVisitor() { public String modifySql(String sql, Database database) { log.info("Liquibase executing SQL [" + sql + "]..."); return sql; } public String getName() { return null; } }; database.execute(new SqlStatement[]{new DropTableStatement(null, "rhq_calltime_data_key", false)}, Arrays.asList(sqlVisitor));*/ try { PreparedStatement statement = AbstractEJB3Test.getConnection().prepareStatement("DROP TABLE rhq_calltime_data_key"); statement.execute(); } catch (SQLException e) { // ignore } } /** * Launches Ant and runs the default target in the given build file. * * @param buildFile the build file that Ant will run * @param customTaskDefs the properties file found in classloader that contains all the taskdef definitions * @param properties set of properties to set for the ANT task to access * @param logFile where Ant messages will be logged (in addition to the app server's log file) * @throws RuntimeException */ private static void startAnt(URL buildFile, String customTaskDefs, Properties properties, File logFile) { PrintWriter logFileOutput = null; try { logFileOutput = new PrintWriter(new FileOutputStream(logFile)); ClassLoader classLoader = DbSetupUtility.class.getClassLoader(); Properties taskDefs = new Properties(); InputStream taskDefsStream = classLoader.getResourceAsStream(customTaskDefs); try { taskDefs.load(taskDefsStream); } finally { taskDefsStream.close(); } Project project = new Project(); project.setCoreLoader(classLoader); project.init(); for (Map.Entry<Object, Object> property : properties.entrySet()) { project.setProperty(property.getKey().toString(), property.getValue().toString()); } // notice we add our listener after we set the properties - we do not want the password to be in the log file // our dbupgrade script will echo the property settings, so we can still get the other values project.addBuildListener(new LoggerAntBuildListener(logFileOutput)); for (Map.Entry<Object, Object> taskDef : taskDefs.entrySet()) { project.addTaskDefinition(taskDef.getKey().toString(), Class.forName(taskDef.getValue().toString(), true, classLoader)); } new ProjectHelper2().parse(project, buildFile); project.executeTarget(project.getDefaultTarget()); } catch (Exception e) { throw new RuntimeException("Cannot run Ant on script [" + buildFile + "] - cause: " + e, e); } finally { if (logFileOutput != null) { logFileOutput.close(); } } } private static class TestDatasourceConfiguration { abstract class Property { public static final String DB_CONNECTION_URL = "rhq.test.ds.connection-url"; public static final String DB_SERVER_NAME = "rhq.test.ds.server-name"; public static final String DB_NAME = "rhq.test.ds.db-name"; public static final String DB_USER_NAME = "rhq.test.ds.user-name"; public static final String DB_PASSWORD = "rhq.test.ds.password"; } String connectionUrl; String serverName; String dbName; String userName; String password; private TestDatasourceConfiguration(Properties props) { connectionUrl = props.getProperty(Property.DB_CONNECTION_URL); serverName = props.getProperty(Property.DB_SERVER_NAME); dbName = props.getProperty(Property.DB_NAME); userName = props.getProperty(Property.DB_USER_NAME); password = props.getProperty(Property.DB_PASSWORD); } @Override public String toString() { // NOTE: For the sake of security, we don't include the password here. return "{" + "connectionUrl='" + connectionUrl + '\'' + ", serverName='" + serverName + '\'' + ", dbName='" + dbName + '\'' + ", userName='" + userName + '\'' + '}'; } } }