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.
16 package org.wamblee.system.spring.component;
18 import junit.framework.TestCase;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
23 import org.dbunit.DatabaseUnitException;
25 import org.dbunit.database.DatabaseConnection;
26 import org.dbunit.database.DatabaseSequenceFilter;
27 import org.dbunit.database.IDatabaseConnection;
29 import org.dbunit.dataset.FilteredDataSet;
30 import org.dbunit.dataset.IDataSet;
31 import org.dbunit.dataset.filter.ITableFilter;
33 import org.dbunit.operation.DatabaseOperation;
35 import org.hibernate.SessionFactory;
37 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
39 import org.springframework.context.ApplicationContext;
41 import org.springframework.jdbc.core.JdbcTemplate;
42 import org.springframework.jdbc.datasource.DataSourceUtils;
44 import org.springframework.orm.hibernate3.HibernateTemplate;
46 import org.springframework.transaction.PlatformTransactionManager;
47 import org.springframework.transaction.TransactionDefinition;
48 import org.springframework.transaction.TransactionStatus;
49 import org.springframework.transaction.support.DefaultTransactionDefinition;
50 import org.springframework.transaction.support.TransactionCallback;
51 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
52 import org.springframework.transaction.support.TransactionTemplate;
54 import org.wamblee.test.spring.TestTransactionCallback;
55 import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
57 import java.sql.Connection;
58 import java.sql.PreparedStatement;
59 import java.sql.ResultSet;
60 import java.sql.SQLException;
62 import java.util.ArrayList;
63 import java.util.Iterator;
64 import java.util.List;
66 import java.util.TreeMap;
68 import javax.sql.DataSource;
72 * Test support class for database testing. Currently, this still requires
73 * the spring platform transaction manager and hibernate template.
75 public class DatabaseTesterComponent {
79 private static final Log LOG = LogFactory.getLog(DatabaseTesterComponent.class);
84 private static final String SCHEMA_PATTERN = "%";
87 * Cached spring application context.
89 private ApplicationContext context;
94 private HibernateTemplate hibernateTemplate;
99 private PlatformTransactionManager transactionManager;
104 private DataSource dataSource;
107 * Creates a new DatabaseTesterComponent object.
109 * @param aHibernateTemplate DOCUMENT ME!
110 * @param aTransactionManager DOCUMENT ME!
111 * @param aDataSource DOCUMENT ME!
113 public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
114 PlatformTransactionManager aTransactionManager, DataSource aDataSource) {
115 hibernateTemplate = aHibernateTemplate;
116 transactionManager = aTransactionManager;
117 dataSource = aDataSource;
123 * @return Hibernate session factory.
125 protected SessionFactory getSessionFactory() {
126 return hibernateTemplate.getSessionFactory();
130 * Performs common initialization for test cases:
132 * <li>Cleaning the database.</li>
137 public void setUp() throws Exception {
138 LOG.info("Performing setUp()");
144 * Performs common tear down after execution of a test case.
145 * Currenlty this method does nothing.
149 protected void tearDown() throws Exception {
156 * @return Transaction manager
158 protected PlatformTransactionManager getTransactionManager() {
159 return transactionManager;
165 * @return Starts a new transaction.
167 protected TransactionStatus getTransaction() {
168 DefaultTransactionDefinition def = new DefaultTransactionDefinition();
169 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
171 return getTransactionManager().getTransaction(def);
175 * Returns the hibernate template for executing hibernate-specific
178 * @return Hibernate template.
180 public HibernateTemplate getTemplate() {
181 return hibernateTemplate;
185 * Flushes the session. Should be called after some Hibernate work
186 * and before JDBC is used to check results.
188 public void flush() {
189 getTemplate().flush();
193 * Flushes the session first and then removes all objects from the
194 * Session cache. Should be called after some Hibernate work and before
195 * JDBC is used to check results.
197 public void clear() {
199 getTemplate().clear();
203 * Evicts the object from the session. This is essential for the
204 * implementation of unit tests where first an object is saved and is
205 * retrieved later. By removing the object from the session, Hibernate
206 * must retrieve the object again from the database.
210 protected void evict(Object aObject) {
211 getTemplate().evict(aObject);
215 * Gets the connection.
217 * @return Connection.
219 public Connection getConnection() {
220 return DataSourceUtils.getConnection(getDataSource());
226 * @throws SQLException DOCUMENT ME!
228 public void cleanDatabase() throws SQLException {
229 if (!isDatabaseConfigured()) {
233 String[] tables = getTableNames();
236 IDatabaseConnection connection = new DatabaseConnection(getConnection());
237 ITableFilter filter = new DatabaseSequenceFilter(connection,
239 IDataSet dataset = new FilteredDataSet(filter,
240 connection.createDataSet(tables));
242 DatabaseOperation.DELETE_ALL.execute(connection, dataset);
243 } catch (DatabaseUnitException e) {
244 SQLException exc = new SQLException(e.getMessage());
253 * @return DOCUMENT ME!
255 * @throws SQLException
257 public String[] getTableNames() throws SQLException {
258 List<String> result = new ArrayList<String>();
259 LOG.debug("Getting database table names to clean (schema: '"
260 + SCHEMA_PATTERN + "'");
262 ResultSet tables = getConnection().getMetaData()
263 .getTables(null, SCHEMA_PATTERN, "%", new String[] { "TABLE" });
265 while (tables.next()) {
266 String table = tables.getString("TABLE_NAME");
268 // Make sure we do not touch hibernate's specific
269 // infrastructure tables.
270 if (!table.toLowerCase().startsWith("hibernate")) {
272 LOG.debug("Adding " + table
273 + " to list of tables to be cleaned.");
277 return (String[]) result.toArray(new String[0]);
283 * @param aTableList DOCUMENT ME!
285 * @throws SQLException
287 public void emptyTables(List aTableList) throws SQLException {
288 Iterator liTable = aTableList.iterator();
290 while (liTable.hasNext()) {
291 emptyTable((String) liTable.next());
298 * @param aTable DOCUMENT ME!
300 * @throws SQLException
302 public void emptyTable(String aTable) throws SQLException {
303 executeSql("delete from " + aTable);
309 * @param aTable DOCUMENT ME!
311 * @throws SQLException
313 public void dropTable(String aTable) throws SQLException {
314 executeQuery("drop table " + aTable);
318 * Executes an SQL statement within a transaction.
320 * @param aSql SQL statement.
322 * @return Return code of the corresponding JDBC call.
324 public int executeSql(final String aSql) {
325 return executeSql(aSql, new Object[0]);
329 * Executes an SQL statement within a transaction. See {@link
330 * #setPreparedParam(int, PreparedStatement, Object)}for details on
331 * supported argument types.
333 * @param aSql SQL statement.
334 * @param aArg Argument of the sql statement.
336 * @return Return code of the corresponding JDBC call.
338 public int executeSql(final String aSql, final Object aArg) {
339 return executeSql(aSql, new Object[] { aArg });
343 * Executes an sql statement. See {@link #setPreparedParam(int,
344 * PreparedStatement, Object)}for details on supported argument types.
346 * @param aSql SQL query to execute.
347 * @param aArgs Arguments.
349 * @return Number of rows updated.
351 public int executeSql(final String aSql, final Object[] aArgs) {
352 Map results = executeTransaction(new TestTransactionCallback() {
353 public Map execute() throws Exception {
354 JdbcTemplate template = new JdbcTemplate(getDataSource());
355 int result = template.update(aSql,
358 Map<String, Integer> map = new TreeMap<String, Integer>();
359 map.put("result", new Integer(result));
365 return ((Integer) results.get("result")).intValue();
369 * Executes a transaction with a result.
371 * @param aCallback Callback to do your transactional work.
375 public Object executeTransaction(TransactionCallback aCallback) {
376 TransactionTemplate lTemplate = new TransactionTemplate(getTransactionManager());
378 return lTemplate.execute(aCallback);
382 * Executes a transaction without a result.
384 * @param aCallback Callback to do your transactional work. .
386 public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
387 TransactionTemplate template = new TransactionTemplate(getTransactionManager());
388 template.execute(aCallback);
392 * Executes a transaction with a result, causing the testcase to
393 * fail if any type of exception is thrown.
395 * @param aCallback Code to be executed within the transaction.
399 * @throws RuntimeException DOCUMENT ME!
401 public Map executeTransaction(final TestTransactionCallback aCallback) {
402 return (Map) executeTransaction(new TransactionCallback() {
403 public Object doInTransaction(TransactionStatus aArg) {
405 return aCallback.execute();
406 } catch (Exception e) {
407 // test case must fail.
409 throw new RuntimeException(e);
416 * Executes a transaction with a result, causing the testcase to
417 * fail if any type of exception is thrown.
419 * @param aCallback Code to be executed within the transaction.
421 * @throws RuntimeException DOCUMENT ME!
423 public void executeTransaction(
424 final TestTransactionCallbackWithoutResult aCallback) {
425 executeTransaction(new TransactionCallbackWithoutResult() {
426 public void doInTransactionWithoutResult(TransactionStatus aArg) {
429 } catch (Exception e) {
430 // test case must fail.
431 throw new RuntimeException(e.getMessage(), e);
438 * Executes an SQL query.
440 * @param aSql Query to execute.
442 * @return Result set.
444 public ResultSet executeQuery(String aSql) {
445 return executeQuery(aSql, new Object[0]);
449 * Executes a query with a single argument. See {@link
450 * #setPreparedParam(int, PreparedStatement, Object)}for details on
451 * supported argument types.
454 * @param aArg Argument.
456 * @return Result set.
458 public ResultSet executeQuery(String aSql, Object aArg) {
459 return executeQuery(aSql, new Object[] { aArg });
463 * Executes a query. See {@link #setPreparedParam(int,
464 * PreparedStatement, Object)}for details on supported argument types.
466 * @param aSql Sql query.
467 * @param aArgs Arguments to the query.
469 * @return Result set.
471 * @throws RuntimeException DOCUMENT ME!
473 public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
475 Connection connection = getConnection();
477 PreparedStatement statement = connection.prepareStatement(aSql);
478 setPreparedParams(aArgs, statement);
480 return statement.executeQuery();
481 } catch (SQLException e) {
482 throw new RuntimeException(e);
487 * Sets the values of a prepared statement. See {@link
488 * #setPreparedParam(int, PreparedStatement, Object)}for details on
489 * supported argument types.
491 * @param aArgs Arguments to the prepared statement.
492 * @param aStatement Prepared statement
494 * @throws SQLException
496 private void setPreparedParams(final Object[] aArgs,
497 PreparedStatement aStatement) throws SQLException {
498 for (int i = 1; i <= aArgs.length; i++) {
499 setPreparedParam(i, aStatement, aArgs[i - 1]);
504 * Sets a prepared statement parameter.
506 * @param aIndex Index of the parameter.
507 * @param aStatement Prepared statement.
508 * @param aObject Value Must be of type Integer, Long, or String.
510 * @throws SQLException
512 private void setPreparedParam(int aIndex, PreparedStatement aStatement,
513 Object aObject) throws SQLException {
514 if (aObject instanceof Integer) {
515 aStatement.setInt(aIndex, ((Integer) aObject).intValue());
516 } else if (aObject instanceof Long) {
517 aStatement.setLong(aIndex, ((Integer) aObject).longValue());
518 } else if (aObject instanceof String) {
519 aStatement.setString(aIndex, (String) aObject);
521 TestCase.fail("Unsupported object type for prepared statement: "
522 + aObject.getClass() + " value: " + aObject + " statement: "
530 * @return DOCUMENT ME!
532 private boolean isDatabaseConfigured() {
535 } catch (NoSuchBeanDefinitionException e) {
545 * @return Returns the dataSource.
547 public DataSource getDataSource() {
554 * @param aTable DOCUMENT ME!
558 * @throws SQLException
560 public int getTableSize(final String aTable) throws SQLException {
561 ResultSet resultSet = executeQuery("select * from " + aTable);
564 while (resultSet.next()) {
574 * @param aResultSet DOCUMENT ME!
576 * @return DOCUMENT ME!
578 * @throws SQLException DOCUMENT ME!
580 public int countResultSet(ResultSet aResultSet) throws SQLException {
583 while (aResultSet.next()) {