/* vim: set ts=2 et sw=2 cindent fo=qroca: */
package com.globant.katari.tools;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
import com.globant.katari.tools.database.MySqlDropAllObjects;
import com.globant.katari.tools.database.PostgreSqlDropAllObjects;
import com.globant.katari.tools.database.OracleDropAllObjects;
/** DatabaseTestSupport abstract class for classes that can drop objects and
* run sql sentences.
*/
public abstract class DatabaseTestSupport {
/** A logger.
*/
private static Logger log =
LoggerFactory.getLogger(DatabaseTestSupport.class);
/** The data base drop objects Map.
*/
private static Map<String, DatabaseTestSupport> dbDropObjectsMap
= new HashMap<String, DatabaseTestSupport>();
/** Registers a handler for an additional dialect.
*
* @param dialect The hibernate dialect. It cannot be null.
*
* @param support The DatabaseTestSupport that will handle the dialect. It
* cannot be null.
*/
public static void registerDialect(final String dialect, final
DatabaseTestSupport support) {
dbDropObjectsMap.put(dialect, support);
}
static {
initialize();
}
/** Registers the classes responsible for effectively dropping the database
* objects.
*/
private static void initialize() {
registerDialect("org.hibernate.dialect.MySQLDialect",
new MySqlDropAllObjects());
registerDialect("org.hibernate.dialect.MySQL5InnoDBDialect",
new MySqlDropAllObjects());
registerDialect("org.hibernate.dialect.PostgreSQLDialect",
new PostgreSqlDropAllObjects());
registerDialect("org.hibernate.dialect.Oracle8iDialect",
new OracleDropAllObjects());
registerDialect("org.hibernate.dialect.Oracle9Dialect",
new OracleDropAllObjects());
registerDialect("org.hibernate.dialect.Oracle9iDialect",
new OracleDropAllObjects());
registerDialect("org.hibernate.dialect.Oracle10gDialect",
new OracleDropAllObjects());
}
/** Returns the corresponding DatabaseTestSupport instance.
*
* The returned instance depends on the database used.
*
* @param session The session factory. It cannot be null.
*
* @return The correspinding DatabaseTestSupport.
*/
public static DatabaseTestSupport create(
final LocalSessionFactoryBean session) {
Validate.notNull(session, "The session factory cannot be null.");
String dialect = (String) session.getConfiguration()
.getProperties().get("hibernate.dialect");
DatabaseTestSupport databaseTestSupport = dbDropObjectsMap.get(dialect);
Validate.notNull(databaseTestSupport, "Dialect " + dialect
+ " not supported.");
return databaseTestSupport;
}
/** Drops all tables from the data base.
*
* Checks if the marker table exists and contains 'YES DROP ME'. If it does,
* it drops all objects in the database, excluding the marker table.
*
* @param connection The database connection to use. It cannot be null.
*
* @param markerTable The marker table name. This table must exist and have
* one column named drop_database with 'YES DROP ME'.
*/
public void dropAll(final Connection connection, final String markerTable) {
try {
assertDevelopmentDatabase(connection, markerTable);
doDropAll(connection, markerTable);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
/** Deletes all rows from all tables in the data base.
*
* Checks if the marker table exists and contains 'YES DROP ME'. If it does,
* it deletes all rows in the database, excluding the marker table.
*
* @param connection The database connection to use. It cannot be null.
*
* @param markerTable The marker table name. This table must exist and have
* one column named drop_database with 'YES DROP ME'.
*/
public void deleteAll(final Connection connection,
final String markerTable) {
try {
assertDevelopmentDatabase(connection, markerTable);
doDeleteAll(connection, markerTable);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
/** Initializes the auto increment columns to a predefined value.
*
* @param connection The database connection to use. It cannot be null.
*
* @param initialValue the initial value to use for autoincrement.
*
* @throws Exception in case of error.
*/
public void initializeAutoincrement(final Connection connection,
final int initialValue) throws Exception {
try {
doInitializeAutoincrement(connection, initialValue);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
/** Template method to drop all tables from the data base.
*
* @param connection The database connectino to use to drop all the objects.
* It cannot be null.
*
* @param markerTable The name of the marker table. It cannot be null.
*
* @throws Exception in case of error.
*/
protected abstract void doDropAll(final Connection connection, final String
markerTable) throws Exception;
/** Template method to delete all rows from all tables in the data base.
*
* @param connection The database connectino to use to drop all the objects.
* It cannot be null.
*
* @param markerTable The name of the marker table. It cannot be null.
*
* @throws Exception in case of error.
*/
protected abstract void doDeleteAll(final Connection connection, final String
markerTable) throws Exception;
/** Template method to initialize the auto increment columns to a predefined
* value.
*
* @param connection The database connectino to use to drop all the objects.
* It cannot be null.
*
* @param initialValue the initial value to use for autoincrement.
*
* @throws Exception in case of error.
*/
protected abstract void doInitializeAutoincrement(
final Connection connection, final int initialValue) throws Exception;
/** Runs a set of sql sentences stored in a file.
*
* Sentences in the file are delimited by a line ending in ;.
*
* @param connection the database connection to use to run the sql sentences.
* It cannot be null.
*
* @param fileName the String with the file name.
*/
public void runSqlSentences(final Connection connection, final String
fileName) {
log.trace("Entering runSqlSentences('" + fileName + "')");
Validate.notNull(fileName, "The file name cannot be null.");
String sentence = null;
try {
Statement statement = connection.createStatement();
SqlSentencesParser parser = new SqlSentencesParser(fileName);
while (null != (sentence = parser.readSentence())) {
statement.execute(StringEscapeUtils.unescapeJava(sentence));
}
} catch (Exception e) {
System.out.println("Error executing " + sentence);
e.printStackTrace();
System.exit(1);
}
log.trace("Leaving runSqlSentences");
}
/** Verifies if it is a development database, that is, if it has the mark
* table.
*
* It throws an exception if it is not a development database.
*
* @param connection The connection to execute sql sentences. It cannot be
* null.
*
* @param markerTable The marker table name. Is the marker of the db of
* permision.
*
* @throws SQLException if a database access error occurs.
*/
private void assertDevelopmentDatabase(final Connection connection,
final String markerTable) throws SQLException {
Validate.notNull(connection, "The connection cannot be null.");
Statement st = connection.createStatement();
/* Check if we are running against a scratch database.
*/
log.debug("Verifying if it is a test database");
ResultSet rs = null;
try {
rs = st.executeQuery("select drop_database from " + markerTable);
} catch (SQLException e) {
// An exeption. Give some explanation just in case.
log.info("An exception was caught selecting from {}"
+ ". It is probable because the table does not exist. "
+ "Please create it with:", markerTable);
log.info("create table {} (drop_database varchar (50));", markerTable);
log.info("insert into {} values ('YES, DROP ME');", markerTable);
System.out.println("An exception was caught selecting from "
+ markerTable + ". It is probable because the table does"
+ " not exist. Please create it with:");
System.out.println("create table " + markerTable
+ "(drop_database varchar (50));");
System.out.println("insert into " + markerTable
+ " values ('YES, DROP ME');");
throw e;
}
String message = null;
if (rs.next()) {
message = rs.getString("drop_database");
}
if (message == null) {
throw new RuntimeException("Marker table does not contain a row");
}
if (!message.equals("YES, DROP ME")) {
throw new RuntimeException("Marker table does not contain the"
+ " correct row");
}
}
/** Checks if the database schema matches the hibernate session factory.
*
* @param sessionFactory the hibernate session factory. It cannot be null.
*
* @param connection the database connection. It cannot be null.
*
* @return returns true if the database schema matches the session factory,
* false otherwise.
*/
public boolean isUpToDate(final LocalSessionFactoryBean sessionFactory,
final Connection connection) {
Configuration configuration = sessionFactory.getConfiguration();
Dialect dialect = Dialect.getDialect(configuration.getProperties());
DatabaseMetadata databaseMetadata = null;
try {
databaseMetadata = new DatabaseMetadata(connection, dialect, false);
} catch (Exception e) {
System.out.println("Error obtaining database metadata");
e.printStackTrace();
System.exit(1);
}
try {
configuration.validateSchema(dialect, databaseMetadata);
return true;
} catch (HibernateException e) {
return false;
}
}
}