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;
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.beans.factory.config.BeanDefinition;
46 import org.springframework.beans.factory.support.RootBeanDefinition;
47 import org.springframework.context.ApplicationContext;
48 import org.springframework.context.support.ClassPathXmlApplicationContext;
49 import org.springframework.context.support.GenericApplicationContext;
50 import org.springframework.jdbc.core.JdbcTemplate;
51 import org.springframework.jdbc.datasource.DataSourceUtils;
52 import org.springframework.jdbc.datasource.DriverManagerDataSource;
53 import org.springframework.orm.hibernate3.HibernateTemplate;
54 import org.springframework.transaction.PlatformTransactionManager;
55 import org.springframework.transaction.TransactionDefinition;
56 import org.springframework.transaction.TransactionStatus;
57 import org.springframework.transaction.support.DefaultTransactionDefinition;
58 import org.springframework.transaction.support.TransactionCallback;
59 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
60 import org.springframework.transaction.support.TransactionTemplate;
61 import org.wamblee.general.BeanKernel;
62 import org.wamblee.persistence.hibernate.HibernateMappingFiles;
63 import org.wamblee.test.spring.SpringConfigFiles;
64 import org.wamblee.test.spring.TestSpringBeanFactory;
65 import org.wamblee.test.spring.TestTransactionCallback;
66 import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
69 * Test support class for database testing. Currently, this still requires the
70 * spring platform transaction manager and hibernate template.
72 public class DatabaseTesterComponent {
74 private static final Log LOG = LogFactory
75 .getLog(DatabaseTesterComponent.class);
80 private static final String SCHEMA_PATTERN = "%";
83 * Cached spring application context.
85 private ApplicationContext _context;
87 private HibernateTemplate _hibernateTemplate;
89 private PlatformTransactionManager _transactionManager;
91 private DataSource _dataSource;
93 public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
94 PlatformTransactionManager aTransactionManager,
95 DataSource aDataSource) {
96 _hibernateTemplate = aHibernateTemplate;
97 _transactionManager = aTransactionManager;
98 _dataSource = aDataSource;
102 * @return Hibernate session factory.
104 protected SessionFactory getSessionFactory() {
105 return _hibernateTemplate.getSessionFactory();
109 * Performs common initialization for test cases:
111 * <li>Cleaning the database. </li>
116 public void setUp() throws Exception {
117 LOG.info("Performing setUp()");
123 * Performs common tear down after execution of a test case. Currenlty this
124 * method does nothing.
128 protected void tearDown() throws Exception {
133 * @return Transaction manager
135 protected PlatformTransactionManager getTransactionManager() {
136 return _transactionManager;
140 * @return Starts a new transaction.
142 protected TransactionStatus getTransaction() {
143 DefaultTransactionDefinition def = new DefaultTransactionDefinition();
144 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
146 return getTransactionManager().getTransaction(def);
150 * Returns the hibernate template for executing hibernate-specific
153 * @return Hibernate template.
155 public HibernateTemplate getTemplate() {
156 return _hibernateTemplate;
160 * Flushes the session. Should be called after some Hibernate work and
161 * before JDBC is used to check results.
164 public void flush() {
165 getTemplate().flush();
169 * Flushes the session first and then removes all objects from the Session
170 * cache. Should be called after some Hibernate work and before JDBC is used
174 public void clear() {
176 getTemplate().clear();
180 * Evicts the object from the session. This is essential for the
181 * implementation of unit tests where first an object is saved and is
182 * retrieved later. By removing the object from the session, Hibernate must
183 * retrieve the object again from the database.
187 protected void evict(Object aObject) {
188 getTemplate().evict(aObject);
192 * Gets the connection.
194 * @return Connection.
196 public Connection getConnection() {
197 return DataSourceUtils.getConnection(getDataSource());
200 public void cleanDatabase() throws SQLException {
202 if (!isDatabaseConfigured()) {
206 String[] tables = getTableNames();
209 IDatabaseConnection connection = new DatabaseConnection(
211 ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
212 IDataSet dataset = new FilteredDataSet(filter, connection
213 .createDataSet(tables));
215 DatabaseOperation.DELETE_ALL.execute(connection, dataset);
216 } catch (DatabaseUnitException e) {
217 SQLException exc = new SQLException(e.getMessage());
224 * @throws SQLException
226 public String[] getTableNames() throws SQLException {
228 List<String> result = new ArrayList<String>();
229 LOG.debug("Getting database table names to clean (schema: '"
230 + SCHEMA_PATTERN + "'");
232 ResultSet tables = getConnection().getMetaData().getTables(null,
233 SCHEMA_PATTERN, "%", new String[] { "TABLE" });
234 while (tables.next()) {
235 String table = tables.getString("TABLE_NAME");
236 // Make sure we do not touch hibernate's specific
237 // infrastructure tables.
238 if (!table.toLowerCase().startsWith("hibernate")) {
240 LOG.debug("Adding " + table
241 + " to list of tables to be cleaned.");
244 return (String[]) result.toArray(new String[0]);
249 * @throws SQLException
251 public void emptyTables(List aTableList) throws SQLException {
252 Iterator liTable = aTableList.iterator();
253 while (liTable.hasNext()) {
254 emptyTable((String) liTable.next());
260 * @throws SQLException
262 public void emptyTable(String aTable) throws SQLException {
263 executeSql("delete from " + aTable);
268 * @throws SQLException
270 public void dropTable(String aTable) throws SQLException {
271 executeQuery("drop table " + aTable);
275 * Executes an SQL statement within a transaction.
279 * @return Return code of the corresponding JDBC call.
281 public int executeSql(final String aSql) {
282 return executeSql(aSql, new Object[0]);
286 * Executes an SQL statement within a transaction. See
287 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
288 * supported argument types.
293 * Argument of the sql statement.
294 * @return Return code of the corresponding JDBC call.
296 public int executeSql(final String aSql, final Object aArg) {
297 return executeSql(aSql, new Object[] { aArg });
301 * Executes an sql statement. See
302 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
303 * supported argument types.
306 * SQL query to execute.
309 * @return Number of rows updated.
311 public int executeSql(final String aSql, final Object[] aArgs) {
312 Map results = executeTransaction(new TestTransactionCallback() {
313 public Map execute() throws Exception {
314 JdbcTemplate template = new JdbcTemplate(getDataSource());
315 int result = template.update(aSql, aArgs);
317 Map<String, Integer> map = new TreeMap<String, Integer>();
318 map.put("result", new Integer(result));
324 return ((Integer) results.get("result")).intValue();
328 * Executes a transaction with a result.
331 * Callback to do your transactional work.
334 public Object executeTransaction(TransactionCallback aCallback) {
335 TransactionTemplate lTemplate = new TransactionTemplate(
336 getTransactionManager());
337 return lTemplate.execute(aCallback);
341 * Executes a transaction without a result.
344 * Callback to do your transactional work. .
346 public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
347 TransactionTemplate template = new TransactionTemplate(
348 getTransactionManager());
349 template.execute(aCallback);
353 * Executes a transaction with a result, causing the testcase to fail if any
354 * type of exception is thrown.
357 * Code to be executed within the transaction.
360 public Map executeTransaction(final TestTransactionCallback aCallback) {
361 return (Map) executeTransaction(new TransactionCallback() {
362 public Object doInTransaction(TransactionStatus aArg) {
364 return aCallback.execute();
365 } catch (Exception e) {
366 // test case must fail.
368 throw new RuntimeException(e);
375 * Executes a transaction with a result, causing the testcase to fail if any
376 * type of exception is thrown.
379 * Code to be executed within the transaction.
381 public void executeTransaction(
382 final TestTransactionCallbackWithoutResult aCallback) {
383 executeTransaction(new TransactionCallbackWithoutResult() {
384 public void doInTransactionWithoutResult(TransactionStatus aArg) {
387 } catch (Exception e) {
388 // test case must fail.
389 throw new RuntimeException(e.getMessage(), e);
396 * Executes an SQL query.
400 * @return Result set.
402 public ResultSet executeQuery(String aSql) {
403 return executeQuery(aSql, new Object[0]);
407 * Executes a query with a single argument. See
408 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
409 * supported argument types.
415 * @return Result set.
417 public ResultSet executeQuery(String aSql, Object aArg) {
418 return executeQuery(aSql, new Object[] { aArg });
422 * Executes a query. See
423 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
424 * supported argument types.
429 * Arguments to the query.
430 * @return Result set.
432 public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
434 Connection connection = getConnection();
436 PreparedStatement statement = connection.prepareStatement(aSql);
437 setPreparedParams(aArgs, statement);
439 return statement.executeQuery();
440 } catch (SQLException e) {
441 throw new RuntimeException(e);
446 * Sets the values of a prepared statement. See
447 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
448 * supported argument types.
451 * Arguments to the prepared statement.
454 * @throws SQLException
456 private void setPreparedParams(final Object[] aArgs,
457 PreparedStatement aStatement) throws SQLException {
458 for (int i = 1; i <= aArgs.length; i++) {
459 setPreparedParam(i, aStatement, aArgs[i - 1]);
464 * Sets a prepared statement parameter.
467 * Index of the parameter.
469 * Prepared statement.
471 * Value Must be of type Integer, Long, or String. TODO extend
472 * with more types of values.
473 * @throws SQLException
475 private void setPreparedParam(int aIndex, PreparedStatement aStatement,
476 Object aObject) throws SQLException {
477 if (aObject instanceof Integer) {
478 aStatement.setInt(aIndex, ((Integer) aObject).intValue());
479 } else if (aObject instanceof Long) {
480 aStatement.setLong(aIndex, ((Integer) aObject).longValue());
481 } else if (aObject instanceof String) {
482 aStatement.setString(aIndex, (String) aObject);
484 TestCase.fail("Unsupported object type for prepared statement: "
485 + aObject.getClass() + " value: " + aObject
486 + " statement: " + aStatement);
490 private boolean isDatabaseConfigured() {
493 } catch (NoSuchBeanDefinitionException e) {
500 * @return Returns the dataSource.
502 public DataSource getDataSource() {
508 * @throws SQLException
510 public int getTableSize(final String aTable) throws SQLException {
512 ResultSet resultSet = executeQuery("select * from " + aTable);
515 while (resultSet.next()) {
521 public int countResultSet(ResultSet aResultSet) throws SQLException {
524 while (aResultSet.next()) {