package org.apache.ddlutils; | |
/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF 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 | |
* | |
* 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. | |
*/ | |
import java.io.FileInputStream; | |
import java.io.InputStream; | |
import java.io.StringReader; | |
import java.sql.Connection; | |
import java.sql.ResultSet; | |
import java.sql.Statement; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import javax.sql.DataSource; | |
import junit.framework.AssertionFailedError; | |
import org.apache.commons.beanutils.BeanUtils; | |
import org.apache.commons.beanutils.DynaBean; | |
import org.apache.commons.beanutils.DynaProperty; | |
import org.apache.commons.dbcp.BasicDataSource; | |
import org.apache.ddlutils.io.DataReader; | |
import org.apache.ddlutils.io.DataToDatabaseSink; | |
import org.apache.ddlutils.model.Database; | |
import org.apache.ddlutils.platform.CreationParameters; | |
import org.apache.ddlutils.platform.firebird.FirebirdPlatform; | |
import org.apache.ddlutils.platform.interbase.InterbasePlatform; | |
/** | |
* Base class for database writer tests. | |
* | |
* @version $Revision: 289996 $ | |
*/ | |
public abstract class TestDatabaseWriterBase extends TestPlatformBase | |
{ | |
/** The name of the property that specifies properties file with the settings for the connection to test against. */ | |
public static final String JDBC_PROPERTIES_PROPERTY = "jdbc.properties.file"; | |
/** The prefix for properties of the datasource. */ | |
public static final String DATASOURCE_PROPERTY_PREFIX = "datasource."; | |
/** The prefix for properties for ddlutils. */ | |
public static final String DDLUTILS_PROPERTY_PREFIX = "ddlutils."; | |
/** The property for specifying the platform. */ | |
public static final String DDLUTILS_PLATFORM_PROPERTY = DDLUTILS_PROPERTY_PREFIX + "platform"; | |
/** The property specifying the catalog for the tests. */ | |
public static final String DDLUTILS_CATALOG_PROPERTY = DDLUTILS_PROPERTY_PREFIX + "catalog"; | |
/** The property specifying the schema for the tests. */ | |
public static final String DDLUTILS_SCHEMA_PROPERTY = DDLUTILS_PROPERTY_PREFIX + "schema"; | |
/** The prefix for table creation properties. */ | |
public static final String DDLUTILS_TABLE_CREATION_PREFIX = DDLUTILS_PROPERTY_PREFIX + "tableCreation."; | |
/** The test properties as defined by an external properties file. */ | |
private static Properties _testProps; | |
/** The data source to test against. */ | |
private static DataSource _dataSource; | |
/** The database name. */ | |
private static String _databaseName; | |
/** The database model. */ | |
private Database _model; | |
/** | |
* Creates a new test case instance. | |
*/ | |
public TestDatabaseWriterBase() | |
{ | |
super(); | |
init(); | |
} | |
/** | |
* Returns the test properties. | |
* | |
* @return The properties | |
*/ | |
protected Properties getTestProperties() | |
{ | |
if (_testProps == null) | |
{ | |
String propFile = System.getProperty(JDBC_PROPERTIES_PROPERTY); | |
if (propFile == null) | |
{ | |
throw new RuntimeException("Please specify the properties file via the jdbc.properties.file environment variable"); | |
} | |
try | |
{ | |
InputStream propStream = getClass().getResourceAsStream(propFile); | |
if (propStream == null) | |
{ | |
propStream = new FileInputStream(propFile); | |
} | |
Properties props = new Properties(); | |
props.load(propStream); | |
_testProps = props; | |
} | |
catch (Exception ex) | |
{ | |
throw new RuntimeException(ex); | |
} | |
} | |
return _testProps; | |
} | |
/** | |
* Initializes the test datasource and the platform. | |
*/ | |
private void init() | |
{ | |
// the data source won't change during the tests, hence | |
// it is static and needs to be initialized only once | |
if (_dataSource != null) | |
{ | |
return; | |
} | |
Properties props = getTestProperties(); | |
try | |
{ | |
String dataSourceClass = props.getProperty(DATASOURCE_PROPERTY_PREFIX + "class", BasicDataSource.class.getName()); | |
_dataSource = (DataSource)Class.forName(dataSourceClass).newInstance(); | |
for (Iterator it = props.entrySet().iterator(); it.hasNext();) | |
{ | |
Map.Entry entry = (Map.Entry)it.next(); | |
String propName = (String)entry.getKey(); | |
if (propName.startsWith(DATASOURCE_PROPERTY_PREFIX) && !propName.equals(DATASOURCE_PROPERTY_PREFIX +"class")) | |
{ | |
BeanUtils.setProperty(_dataSource, | |
propName.substring(DATASOURCE_PROPERTY_PREFIX.length()), | |
entry.getValue()); | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
throw new DatabaseOperationException(ex); | |
} | |
_databaseName = props.getProperty(DDLUTILS_PLATFORM_PROPERTY); | |
if (_databaseName == null) | |
{ | |
// property not set, then try to determine | |
_databaseName = new PlatformUtils().determineDatabaseType(_dataSource); | |
if (_databaseName == null) | |
{ | |
throw new DatabaseOperationException("Could not determine platform from datasource, please specify it in the jdbc.properties via the ddlutils.platform property"); | |
} | |
} | |
} | |
/** | |
* Returns the test table creation parameters for the given model. | |
* | |
* @param model The model | |
* @return The creation parameters | |
*/ | |
protected CreationParameters getTableCreationParameters(Database model) | |
{ | |
CreationParameters params = new CreationParameters(); | |
for (Iterator entryIt = _testProps.entrySet().iterator(); entryIt.hasNext();) | |
{ | |
Map.Entry entry = (Map.Entry)entryIt.next(); | |
String name = (String)entry.getKey(); | |
String value = (String)entry.getValue(); | |
if (name.startsWith(DDLUTILS_TABLE_CREATION_PREFIX)) | |
{ | |
name = name.substring(DDLUTILS_TABLE_CREATION_PREFIX.length()); | |
for (int tableIdx = 0; tableIdx < model.getTableCount(); tableIdx++) | |
{ | |
params.addParameter(model.getTable(tableIdx), name, value); | |
} | |
} | |
} | |
return params; | |
} | |
/** | |
* Returns the data source. | |
* | |
* @return The data source | |
*/ | |
protected DataSource getDataSource() | |
{ | |
return _dataSource; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
protected String getDatabaseName() | |
{ | |
return _databaseName; | |
} | |
/** | |
* Returns the database model. | |
* | |
* @return The model | |
*/ | |
protected Database getModel() | |
{ | |
return _model; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
protected void setUp() throws Exception | |
{ | |
super.setUp(); | |
getPlatform().setDataSource(getDataSource()); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
protected void tearDown() throws Exception | |
{ | |
try | |
{ | |
if (_model != null) | |
{ | |
dropDatabase(); | |
_model = null; | |
} | |
} | |
finally | |
{ | |
assertAndEnsureClearDatabase(); | |
super.tearDown(); | |
} | |
} | |
/** | |
* Creates a new database from the given XML database schema. | |
* | |
* @param schemaXml The XML database schema | |
* @return The parsed database model | |
*/ | |
protected Database createDatabase(String schemaXml) throws DatabaseOperationException | |
{ | |
Database model = parseDatabaseFromString(schemaXml); | |
createDatabase(model); | |
return model; | |
} | |
/** | |
* Creates a new database from the given model. | |
* | |
* @param model The model | |
*/ | |
protected void createDatabase(Database model) throws DatabaseOperationException | |
{ | |
try | |
{ | |
_model = model; | |
getPlatform().setSqlCommentsOn(false); | |
getPlatform().createModel(_model, getTableCreationParameters(_model), false, false); | |
} | |
catch (Exception ex) | |
{ | |
throw new DatabaseOperationException(ex); | |
} | |
} | |
/** | |
* Alters the database to match the given model. | |
* | |
* @param schemaXml The model XML | |
* @return The model object | |
*/ | |
protected Database alterDatabase(String schemaXml) throws DatabaseOperationException | |
{ | |
Database model = parseDatabaseFromString(schemaXml); | |
alterDatabase(model); | |
return model; | |
} | |
/** | |
* Alters the database to match the given model. | |
* | |
* @param desiredModel The model | |
*/ | |
protected void alterDatabase(Database desiredModel) throws DatabaseOperationException | |
{ | |
try | |
{ | |
_model = desiredModel; | |
_model.resetDynaClassCache(); | |
Database liveModel = readModelFromDatabase(desiredModel.getName()); | |
getPlatform().setSqlCommentsOn(false); | |
getPlatform().alterModel(liveModel, _model, getTableCreationParameters(_model), false); | |
} | |
catch (Exception ex) | |
{ | |
throw new DatabaseOperationException(ex); | |
} | |
} | |
/** | |
* Inserts data into the database. | |
* | |
* @param dataXml The data xml | |
* @return The database | |
*/ | |
protected Database insertData(String dataXml) throws DatabaseOperationException | |
{ | |
try | |
{ | |
DataReader dataReader = new DataReader(); | |
dataReader.setModel(_model); | |
dataReader.setSink(new DataToDatabaseSink(getPlatform(), _model)); | |
dataReader.getSink().start(); | |
dataReader.parse(new StringReader(dataXml)); | |
dataReader.getSink().end(); | |
return _model; | |
} | |
catch (Exception ex) | |
{ | |
throw new DatabaseOperationException(ex); | |
} | |
} | |
/** | |
* Drops the tables defined in the database model. | |
*/ | |
protected void dropDatabase() throws DatabaseOperationException | |
{ | |
getPlatform().dropModel(_model, true); | |
} | |
/** | |
* Checks that the database is clear, and if not clears it (no tables, sequences etc. left) and | |
* throws an {@link AssertionFailedError}. | |
*/ | |
protected void assertAndEnsureClearDatabase() | |
{ | |
Database liveModel = readModelFromDatabase("tmp"); | |
boolean hasStuff = false; | |
if (liveModel.getTableCount() > 0) | |
{ | |
hasStuff = true; | |
try | |
{ | |
getPlatform().dropModel(liveModel, true); | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Could not clear database", ex); | |
} | |
} | |
if (FirebirdPlatform.DATABASENAME.equals(getPlatform().getName()) || | |
InterbasePlatform.DATABASENAME.equals(getPlatform().getName())) | |
{ | |
Connection connection = null; | |
try | |
{ | |
connection = getPlatform().borrowConnection(); | |
hasStuff = hasStuff | dropTriggers(connection); | |
hasStuff = hasStuff | dropGenerators(connection); | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Could not clear database", ex); | |
} | |
finally | |
{ | |
getPlatform().returnConnection(connection); | |
} | |
} | |
// TODO: Check for sequences | |
if (hasStuff) | |
{ | |
fail("Database is not empty after test"); | |
} | |
} | |
/** | |
* Drops generators left by a test in a Firebird/Interbase database. | |
* | |
* @param connection The database connection | |
* @return Whether generators were dropped | |
*/ | |
private boolean dropGenerators(Connection connection) | |
{ | |
Statement stmt = null; | |
boolean hasGenerators = false; | |
try | |
{ | |
stmt = connection.createStatement(); | |
ResultSet rs = stmt.executeQuery("SELECT RDB$GENERATOR_NAME FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME NOT LIKE '%$%'"); | |
List names = new ArrayList(); | |
while (rs.next()) | |
{ | |
names.add(rs.getString(1)); | |
} | |
rs.close(); | |
for (Iterator it = names.iterator(); it.hasNext();) | |
{ | |
String name = (String)it.next(); | |
if (name.toLowerCase().startsWith("gen_")) | |
{ | |
hasGenerators = true; | |
stmt.execute("DROP GENERATOR " + name); | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Error while dropping the remaining generators", ex); | |
} | |
finally | |
{ | |
if (stmt != null) | |
{ | |
try | |
{ | |
stmt.close(); | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Error while clearing the database", ex); | |
} | |
} | |
} | |
return hasGenerators; | |
} | |
/** | |
* Drops triggers left by a test in a Firebird/Interbase database. | |
* | |
* @param connection The database connection | |
* @return Whether triggers were dropped | |
*/ | |
private boolean dropTriggers(Connection connection) | |
{ | |
Statement stmt = null; | |
boolean hasTriggers = false; | |
try | |
{ | |
stmt = connection.createStatement(); | |
ResultSet rs = stmt.executeQuery("SELECT * FROM RDB$TRIGGERS WHERE RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0"); | |
List names = new ArrayList(); | |
while (rs.next()) | |
{ | |
names.add(rs.getString(1)); | |
} | |
rs.close(); | |
for (Iterator it = names.iterator(); it.hasNext();) | |
{ | |
String name = (String)it.next(); | |
if (name.toLowerCase().startsWith("trg_")) | |
{ | |
hasTriggers = true; | |
stmt.execute("DROP TRIGGER " + name); | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Error while dropping the remaining triggers", ex); | |
} | |
finally | |
{ | |
if (stmt != null) | |
{ | |
try | |
{ | |
stmt.close(); | |
} | |
catch (Exception ex) | |
{ | |
getLog().error("Error while clearing the database", ex); | |
} | |
} | |
} | |
return hasTriggers; | |
} | |
/** | |
* Reads the database model from a live database. | |
* | |
* @param databaseName The name of the resulting database | |
* @return The model | |
*/ | |
protected Database readModelFromDatabase(String databaseName) | |
{ | |
Properties props = getTestProperties(); | |
String catalog = props.getProperty(DDLUTILS_CATALOG_PROPERTY); | |
String schema = props.getProperty(DDLUTILS_SCHEMA_PROPERTY); | |
return getPlatform().readModelFromDatabase(databaseName, catalog, schema, null); | |
} | |
/** | |
* Returns the SQL for altering the live database so that it matches the given model. | |
* | |
* @param desiredModel The desired model | |
* @return The alteration SQL | |
*/ | |
protected String getAlterTablesSql(Database desiredModel) | |
{ | |
Database liveModel = readModelFromDatabase(desiredModel.getName()); | |
return getPlatform().getAlterModelSql(liveModel, desiredModel, getTableCreationParameters(desiredModel)); | |
} | |
/** | |
* Determines the value of the bean's property that has the given name. Depending on the | |
* case-setting of the current builder, the case of teh name is considered or not. | |
* | |
* @param bean The bean | |
* @param propName The name of the property | |
* @return The value | |
*/ | |
protected Object getPropertyValue(DynaBean bean, String propName) | |
{ | |
if (getPlatform().isDelimitedIdentifierModeOn()) | |
{ | |
return bean.get(propName); | |
} | |
else | |
{ | |
DynaProperty[] props = bean.getDynaClass().getDynaProperties(); | |
for (int idx = 0; idx < props.length; idx++) | |
{ | |
if (propName.equalsIgnoreCase(props[idx].getName())) | |
{ | |
return bean.get(props[idx].getName()); | |
} | |
} | |
throw new IllegalArgumentException("The bean has no property with the name "+propName); | |
} | |
} | |
} |