/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.liquibase;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* The LiquibaseCustomTask class provides some common utility functions for performing queries or
* generating UUIDs for new objects.
*/
public abstract class LiquibaseCustomTask {
protected Database database;
protected JdbcConnection connection;
protected CustomTaskLogger logger;
private Map<String, PreparedStatement> preparedStatements;
private int nullType;
protected LiquibaseCustomTask(Database database, CustomTaskLogger logger) {
if (database == null) {
throw new IllegalArgumentException("database is null");
}
if (logger == null) {
throw new IllegalArgumentException("logger is null");
}
if (!(database.getConnection() instanceof JdbcConnection)) {
throw new RuntimeException("database connection is not a JDBC connection");
}
this.database = database;
this.connection = (JdbcConnection) database.getConnection();
this.logger = logger;
// Check which type we need to use for nulls (courtesy of Oracle's moody adapter)
// See the comments on this SO question for details:
// http://stackoverflow.com/questions/11793483/setobject-method-of-preparedstatement
this.nullType =
this.database.getDatabaseProductName().matches(".*(?i:oracle).*") ? Types.VARCHAR : Types.NULL;
this.preparedStatements = new HashMap<String, PreparedStatement>();
}
/**
* Sets the parameter at the specified index to the given value. This method attempts to perform
* safe assignment of parameters across all supported platforms.
*
* @param statement
* the statement on which to set a parameter
*
* @param index
* the index of the parameter to set
*
* @param value
* the value to set
*
* @throws NullPointerException
* if statement is null
*
* @return
* the PreparedStatement being updated
*/
protected PreparedStatement setParameter(PreparedStatement statement, int index, Object value)
throws DatabaseException, SQLException {
if (value != null) {
statement.setObject(index, value);
}
else {
statement.setNull(index, this.nullType);
}
return statement;
}
/**
* Fills the parameters of a prepared statement with the given arguments
*
* @param statement
* the statement to fill
*
* @param argv
* the collection of arguments with which to fill the statement's parameters
*
* @throws NullPointerException
* if statement is null
*
* @return
* the provided PreparedStatement
*/
protected PreparedStatement fillStatementParameters(PreparedStatement statement, Object... argv)
throws DatabaseException, SQLException {
statement.clearParameters();
if (argv != null) {
for (int i = 0; i < argv.length; ++i) {
this.setParameter(statement, i + 1, argv[i]);
}
}
return statement;
}
/**
* Prepares a statement and populates it with the specified arguments, pulling from cache when
* possible.
*
* @param sql
* The SQL to execute. The given SQL may be parameterized.
*
* @param argv
* The arguments to use when executing the given query.
*
* @return
* a PreparedStatement instance representing the specified SQL statement
*/
protected PreparedStatement prepareStatement(String sql, Object... argv)
throws DatabaseException, SQLException {
PreparedStatement statement = this.preparedStatements.get(sql);
if (statement == null) {
statement = this.connection.prepareStatement(sql);
this.preparedStatements.put(sql, statement);
}
return this.fillStatementParameters(statement, argv);
}
/**
* Executes the given SQL query.
*
* @param sql
* The SQL to execute. The given SQL may be parameterized.
*
* @param argv
* The arguments to use when executing the given query.
*
* @return
* A ResultSet instance representing the result of the query.
*/
protected ResultSet executeQuery(String sql, Object... argv) throws DatabaseException, SQLException {
PreparedStatement statement = this.prepareStatement(sql, argv);
return statement.executeQuery();
}
/**
* Executes the given SQL update/insert.
*
* @param sql
* The SQL to execute. The given SQL may be parameterized.
*
* @param argv
* The arguments to use when executing the given update.
*
* @return
* The number of rows affected by the update.
*/
protected int executeUpdate(String sql, Object... argv) throws DatabaseException, SQLException {
PreparedStatement statement = this.prepareStatement(sql, argv);
return statement.executeUpdate();
}
/**
* Generates a 32-character UUID to use with object creation/migration.
* <p></p>
* The UUID is generated by creating a "standard" UUID and removing the hyphens. The UUID may be
* standardized by reinserting the hyphens later, if necessary.
*
* @return
* a 32-character UUID
*/
protected String generateUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* Executes this liquibase upgrade task.
*
* @throws DatabaseException
* if an error occurs while performing a database operation
*
* @throws SQLException
* if an error occurs while executing an SQL statement
*/
public abstract void execute() throws DatabaseException, SQLException;
}