2  * Copyright 2005 the original author or authors.
 
   4  * Licensed under the Apache License, Version 2.0 (the "License");
 
   5  * you may not use this file except in compliance with the License.
 
   6  * You may obtain a copy of the License at
 
   8  *      http://www.apache.org/licenses/LICENSE-2.0
 
  10  * Unless required by applicable law or agreed to in writing, software
 
  11  * distributed under the License is distributed on an "AS IS" BASIS,
 
  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  13  * See the License for the specific language governing permissions and
 
  14  * limitations under the License.
 
  17 package org.wamblee.system.spring.component;
 
  19 import java.sql.Connection;
 
  20 import java.sql.PreparedStatement;
 
  21 import java.sql.ResultSet;
 
  22 import java.sql.SQLException;
 
  23 import java.util.ArrayList;
 
  24 import java.util.Iterator;
 
  25 import java.util.List;
 
  27 import java.util.TreeMap;
 
  29 import javax.sql.DataSource;
 
  31 import junit.framework.TestCase;
 
  33 import org.apache.commons.logging.Log;
 
  34 import org.apache.commons.logging.LogFactory;
 
  35 import org.dbunit.DatabaseUnitException;
 
  36 import org.dbunit.database.DatabaseConnection;
 
  37 import org.dbunit.database.DatabaseSequenceFilter;
 
  38 import org.dbunit.database.IDatabaseConnection;
 
  39 import org.dbunit.dataset.FilteredDataSet;
 
  40 import org.dbunit.dataset.IDataSet;
 
  41 import org.dbunit.dataset.filter.ITableFilter;
 
  42 import org.dbunit.operation.DatabaseOperation;
 
  43 import org.hibernate.SessionFactory;
 
  44 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 
  45 import org.springframework.context.ApplicationContext;
 
  46 import org.springframework.jdbc.core.JdbcTemplate;
 
  47 import org.springframework.jdbc.datasource.DataSourceUtils;
 
  48 import org.springframework.orm.hibernate3.HibernateTemplate;
 
  49 import org.springframework.transaction.PlatformTransactionManager;
 
  50 import org.springframework.transaction.TransactionDefinition;
 
  51 import org.springframework.transaction.TransactionStatus;
 
  52 import org.springframework.transaction.support.DefaultTransactionDefinition;
 
  53 import org.springframework.transaction.support.TransactionCallback;
 
  54 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
 
  55 import org.springframework.transaction.support.TransactionTemplate;
 
  56 import org.wamblee.test.spring.TestTransactionCallback;
 
  57 import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
 
  60  * Test support class for database testing. Currently, this still requires the
 
  61  * spring platform transaction manager and hibernate template.
 
  63 public class DatabaseTesterComponent {
 
  65     private static final Log LOG = LogFactory
 
  66             .getLog(DatabaseTesterComponent.class);
 
  71     private static final String SCHEMA_PATTERN = "%";
 
  74      * Cached spring application context.
 
  76     private ApplicationContext _context;
 
  78     private HibernateTemplate _hibernateTemplate;
 
  80     private PlatformTransactionManager _transactionManager;
 
  82     private DataSource _dataSource;
 
  84     public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
 
  85             PlatformTransactionManager aTransactionManager,
 
  86             DataSource aDataSource) {
 
  87         _hibernateTemplate = aHibernateTemplate;
 
  88         _transactionManager = aTransactionManager;
 
  89         _dataSource = aDataSource;
 
  93      * @return Hibernate session factory.
 
  95     protected SessionFactory getSessionFactory() {
 
  96         return _hibernateTemplate.getSessionFactory();
 
 100      * Performs common initialization for test cases:
 
 102      * <li>Cleaning the database. </li>
 
 107     public void setUp() throws Exception {
 
 108         LOG.info("Performing setUp()");
 
 114      * Performs common tear down after execution of a test case. Currenlty this
 
 115      * method does nothing.
 
 119     protected void tearDown() throws Exception {
 
 124      * @return Transaction manager
 
 126     protected PlatformTransactionManager getTransactionManager() {
 
 127         return _transactionManager;
 
 131      * @return Starts a new transaction.
 
 133     protected TransactionStatus getTransaction() {
 
 134         DefaultTransactionDefinition def = new DefaultTransactionDefinition();
 
 135         def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
 
 137         return getTransactionManager().getTransaction(def);
 
 141      * Returns the hibernate template for executing hibernate-specific
 
 144      * @return Hibernate template.
 
 146     public HibernateTemplate getTemplate() {
 
 147         return _hibernateTemplate;
 
 151      * Flushes the session. Should be called after some Hibernate work and
 
 152      * before JDBC is used to check results.
 
 155     public void flush() {
 
 156         getTemplate().flush();
 
 160      * Flushes the session first and then removes all objects from the Session
 
 161      * cache. Should be called after some Hibernate work and before JDBC is used
 
 165     public void clear() {
 
 167         getTemplate().clear();
 
 171      * Evicts the object from the session. This is essential for the
 
 172      * implementation of unit tests where first an object is saved and is
 
 173      * retrieved later. By removing the object from the session, Hibernate must
 
 174      * retrieve the object again from the database.
 
 178     protected void evict(Object aObject) {
 
 179         getTemplate().evict(aObject);
 
 183      * Gets the connection.
 
 185      * @return Connection.
 
 187     public Connection getConnection() {
 
 188         return DataSourceUtils.getConnection(getDataSource());
 
 191     public void cleanDatabase() throws SQLException {
 
 193         if (!isDatabaseConfigured()) {
 
 197         String[] tables = getTableNames();
 
 200             IDatabaseConnection connection = new DatabaseConnection(
 
 202             ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
 
 203             IDataSet dataset = new FilteredDataSet(filter, connection
 
 204                     .createDataSet(tables));
 
 206             DatabaseOperation.DELETE_ALL.execute(connection, dataset);
 
 207         } catch (DatabaseUnitException e) {
 
 208             SQLException exc = new SQLException(e.getMessage());
 
 215      * @throws SQLException
 
 217     public String[] getTableNames() throws SQLException {
 
 219         List<String> result = new ArrayList<String>();
 
 220         LOG.debug("Getting database table names to clean (schema: '"
 
 221                 + SCHEMA_PATTERN + "'");
 
 223         ResultSet tables = getConnection().getMetaData().getTables(null,
 
 224                 SCHEMA_PATTERN, "%", new String[] { "TABLE" });
 
 225         while (tables.next()) {
 
 226             String table = tables.getString("TABLE_NAME");
 
 227             // Make sure we do not touch hibernate's specific
 
 228             // infrastructure tables.
 
 229             if (!table.toLowerCase().startsWith("hibernate")) {
 
 231                 LOG.debug("Adding " + table
 
 232                         + " to list of tables to be cleaned.");
 
 235         return (String[]) result.toArray(new String[0]);
 
 240      * @throws SQLException
 
 242     public void emptyTables(List aTableList) throws SQLException {
 
 243         Iterator liTable = aTableList.iterator();
 
 244         while (liTable.hasNext()) {
 
 245             emptyTable((String) liTable.next());
 
 251      * @throws SQLException
 
 253     public void emptyTable(String aTable) throws SQLException {
 
 254         executeSql("delete from " + aTable);
 
 259      * @throws SQLException
 
 261     public void dropTable(String aTable) throws SQLException {
 
 262         executeQuery("drop table " + aTable);
 
 266      * Executes an SQL statement within a transaction.
 
 270      * @return Return code of the corresponding JDBC call.
 
 272     public int executeSql(final String aSql) {
 
 273         return executeSql(aSql, new Object[0]);
 
 277      * Executes an SQL statement within a transaction. See
 
 278      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
 
 279      * supported argument types.
 
 284      *            Argument of the sql statement.
 
 285      * @return Return code of the corresponding JDBC call.
 
 287     public int executeSql(final String aSql, final Object aArg) {
 
 288         return executeSql(aSql, new Object[] { aArg });
 
 292      * Executes an sql statement. See
 
 293      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
 
 294      * supported argument types.
 
 297      *            SQL query to execute.
 
 300      * @return Number of rows updated.
 
 302     public int executeSql(final String aSql, final Object[] aArgs) {
 
 303         Map results = executeTransaction(new TestTransactionCallback() {
 
 304             public Map execute() throws Exception {
 
 305                 JdbcTemplate template = new JdbcTemplate(getDataSource());
 
 306                 int result = template.update(aSql, aArgs);
 
 308                 Map<String, Integer> map = new TreeMap<String, Integer>();
 
 309                 map.put("result", new Integer(result));
 
 315         return ((Integer) results.get("result")).intValue();
 
 319      * Executes a transaction with a result.
 
 322      *            Callback to do your transactional work.
 
 325     public Object executeTransaction(TransactionCallback aCallback) {
 
 326         TransactionTemplate lTemplate = new TransactionTemplate(
 
 327                 getTransactionManager());
 
 328         return lTemplate.execute(aCallback);
 
 332      * Executes a transaction without a result.
 
 335      *            Callback to do your transactional work. .
 
 337     public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
 
 338         TransactionTemplate template = new TransactionTemplate(
 
 339                 getTransactionManager());
 
 340         template.execute(aCallback);
 
 344      * Executes a transaction with a result, causing the testcase to fail if any
 
 345      * type of exception is thrown.
 
 348      *            Code to be executed within the transaction.
 
 351     public Map executeTransaction(final TestTransactionCallback aCallback) {
 
 352         return (Map) executeTransaction(new TransactionCallback() {
 
 353             public Object doInTransaction(TransactionStatus aArg) {
 
 355                     return aCallback.execute();
 
 356                 } catch (Exception e) {
 
 357                     // test case must fail.
 
 359                     throw new RuntimeException(e);
 
 366      * Executes a transaction with a result, causing the testcase to fail if any
 
 367      * type of exception is thrown.
 
 370      *            Code to be executed within the transaction.
 
 372     public void executeTransaction(
 
 373             final TestTransactionCallbackWithoutResult aCallback) {
 
 374         executeTransaction(new TransactionCallbackWithoutResult() {
 
 375             public void doInTransactionWithoutResult(TransactionStatus aArg) {
 
 378                 } catch (Exception e) {
 
 379                     // test case must fail.
 
 380                     throw new RuntimeException(e.getMessage(), e);
 
 387      * Executes an SQL query.
 
 391      * @return Result set.
 
 393     public ResultSet executeQuery(String aSql) {
 
 394         return executeQuery(aSql, new Object[0]);
 
 398      * Executes a query with a single argument. See
 
 399      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
 
 400      * supported argument types.
 
 406      * @return Result set.
 
 408     public ResultSet executeQuery(String aSql, Object aArg) {
 
 409         return executeQuery(aSql, new Object[] { aArg });
 
 413      * Executes a query. See
 
 414      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
 
 415      * supported argument types.
 
 420      *            Arguments to the query.
 
 421      * @return Result set.
 
 423     public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
 
 425             Connection connection = getConnection();
 
 427             PreparedStatement statement = connection.prepareStatement(aSql);
 
 428             setPreparedParams(aArgs, statement);
 
 430             return statement.executeQuery();
 
 431         } catch (SQLException e) {
 
 432             throw new RuntimeException(e);
 
 437      * Sets the values of a prepared statement. See
 
 438      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
 
 439      * supported argument types.
 
 442      *            Arguments to the prepared statement.
 
 445      * @throws SQLException
 
 447     private void setPreparedParams(final Object[] aArgs,
 
 448             PreparedStatement aStatement) throws SQLException {
 
 449         for (int i = 1; i <= aArgs.length; i++) {
 
 450             setPreparedParam(i, aStatement, aArgs[i - 1]);
 
 455      * Sets a prepared statement parameter.
 
 458      *            Index of the parameter.
 
 460      *            Prepared statement.
 
 462      *            Value Must be of type Integer, Long, or String. 
 
 463      * @throws SQLException
 
 465     private void setPreparedParam(int aIndex, PreparedStatement aStatement,
 
 466             Object aObject) throws SQLException {
 
 467         if (aObject instanceof Integer) {
 
 468             aStatement.setInt(aIndex, ((Integer) aObject).intValue());
 
 469         } else if (aObject instanceof Long) {
 
 470             aStatement.setLong(aIndex, ((Integer) aObject).longValue());
 
 471         } else if (aObject instanceof String) {
 
 472             aStatement.setString(aIndex, (String) aObject);
 
 474             TestCase.fail("Unsupported object type for prepared statement: "
 
 475                     + aObject.getClass() + " value: " + aObject
 
 476                     + " statement: " + aStatement);
 
 480     private boolean isDatabaseConfigured() {
 
 483         } catch (NoSuchBeanDefinitionException e) {
 
 490      * @return Returns the dataSource.
 
 492     public DataSource getDataSource() {
 
 498      * @throws SQLException
 
 500     public int getTableSize(final String aTable) throws SQLException {
 
 502         ResultSet resultSet = executeQuery("select * from " + aTable);
 
 505         while (resultSet.next()) {
 
 511     public int countResultSet(ResultSet aResultSet) throws SQLException {
 
 514         while (aResultSet.next()) {