(no commit message)
[utils] / system / spring / src / test / java / org / wamblee / system / spring / DatabaseTesterComponent.java
1 /*
2  * Copyright 2005 the original author or authors.
3  * 
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
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
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.
15  */
16
17 package org.wamblee.system.spring;
18
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;
26 import java.util.Map;
27 import java.util.TreeMap;
28
29 import javax.sql.DataSource;
30
31 import junit.framework.TestCase;
32
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;
67
68 /**
69  * Test support class for database testing. Currently, this still requires the
70  * spring platform transaction manager and hibernate template.
71  */
72 public class DatabaseTesterComponent {
73
74     private static final Log LOG = LogFactory
75             .getLog(DatabaseTesterComponent.class);
76
77     /**
78      * Schema pattern.
79      */
80     private static final String SCHEMA_PATTERN = "%";
81
82     /**
83      * Cached spring application context.
84      */
85     private ApplicationContext _context;
86
87     private HibernateTemplate _hibernateTemplate;
88
89     private PlatformTransactionManager _transactionManager;
90
91     private DataSource _dataSource;
92
93     public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
94             PlatformTransactionManager aTransactionManager,
95             DataSource aDataSource) {
96         _hibernateTemplate = aHibernateTemplate;
97         _transactionManager = aTransactionManager;
98         _dataSource = aDataSource;
99     }
100
101     /**
102      * @return Hibernate session factory.
103      */
104     protected SessionFactory getSessionFactory() {
105         return _hibernateTemplate.getSessionFactory();
106     }
107
108     /**
109      * Performs common initialization for test cases:
110      * <ul>
111      * <li>Cleaning the database. </li>
112      * </ul>
113      * 
114      * @throws Exception
115      */
116     public void setUp() throws Exception {
117         LOG.info("Performing setUp()");
118
119         cleanDatabase();
120     }
121
122     /**
123      * Performs common tear down after execution of a test case. Currenlty this
124      * method does nothing.
125      * 
126      * @throws Exception
127      */
128     protected void tearDown() throws Exception {
129         // Empty
130     }
131
132     /**
133      * @return Transaction manager
134      */
135     protected PlatformTransactionManager getTransactionManager() {
136         return _transactionManager;
137     }
138
139     /**
140      * @return Starts a new transaction.
141      */
142     protected TransactionStatus getTransaction() {
143         DefaultTransactionDefinition def = new DefaultTransactionDefinition();
144         def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
145
146         return getTransactionManager().getTransaction(def);
147     }
148
149     /**
150      * Returns the hibernate template for executing hibernate-specific
151      * functionality.
152      * 
153      * @return Hibernate template.
154      */
155     public HibernateTemplate getTemplate() {
156         return _hibernateTemplate;
157     }
158
159     /**
160      * Flushes the session. Should be called after some Hibernate work and
161      * before JDBC is used to check results.
162      * 
163      */
164     public void flush() {
165         getTemplate().flush();
166     }
167
168     /**
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
171      * to check results.
172      * 
173      */
174     public void clear() {
175         flush();
176         getTemplate().clear();
177     }
178
179     /**
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.
184      * 
185      * @param aObject
186      */
187     protected void evict(Object aObject) {
188         getTemplate().evict(aObject);
189     }
190
191     /**
192      * Gets the connection.
193      * 
194      * @return Connection.
195      */
196     public Connection getConnection() {
197         return DataSourceUtils.getConnection(getDataSource());
198     }
199
200     public void cleanDatabase() throws SQLException {
201
202         if (!isDatabaseConfigured()) {
203             return;
204         }
205
206         String[] tables = getTableNames();
207
208         try {
209             IDatabaseConnection connection = new DatabaseConnection(
210                     getConnection());
211             ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
212             IDataSet dataset = new FilteredDataSet(filter, connection
213                     .createDataSet(tables));
214
215             DatabaseOperation.DELETE_ALL.execute(connection, dataset);
216         } catch (DatabaseUnitException e) {
217             SQLException exc = new SQLException(e.getMessage());
218             exc.initCause(e);
219             throw exc;
220         }
221     }
222
223     /**
224      * @throws SQLException
225      */
226     public String[] getTableNames() throws SQLException {
227
228         List<String> result = new ArrayList<String>();
229         LOG.debug("Getting database table names to clean (schema: '"
230                 + SCHEMA_PATTERN + "'");
231
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")) {
239                 result.add(table);
240                 LOG.debug("Adding " + table
241                         + " to list of tables to be cleaned.");
242             }
243         }
244         return (String[]) result.toArray(new String[0]);
245     }
246
247     /**
248      * @return
249      * @throws SQLException
250      */
251     public void emptyTables(List aTableList) throws SQLException {
252         Iterator liTable = aTableList.iterator();
253         while (liTable.hasNext()) {
254             emptyTable((String) liTable.next());
255         }
256     }
257
258     /**
259      * @return
260      * @throws SQLException
261      */
262     public void emptyTable(String aTable) throws SQLException {
263         executeSql("delete from " + aTable);
264     }
265
266     /**
267      * @return
268      * @throws SQLException
269      */
270     public void dropTable(String aTable) throws SQLException {
271         executeQuery("drop table " + aTable);
272     }
273
274     /**
275      * Executes an SQL statement within a transaction.
276      * 
277      * @param aSql
278      *            SQL statement.
279      * @return Return code of the corresponding JDBC call.
280      */
281     public int executeSql(final String aSql) {
282         return executeSql(aSql, new Object[0]);
283     }
284
285     /**
286      * Executes an SQL statement within a transaction. See
287      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
288      * supported argument types.
289      * 
290      * @param aSql
291      *            SQL statement.
292      * @param aArg
293      *            Argument of the sql statement.
294      * @return Return code of the corresponding JDBC call.
295      */
296     public int executeSql(final String aSql, final Object aArg) {
297         return executeSql(aSql, new Object[] { aArg });
298     }
299
300     /**
301      * Executes an sql statement. See
302      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
303      * supported argument types.
304      * 
305      * @param aSql
306      *            SQL query to execute.
307      * @param aArgs
308      *            Arguments.
309      * @return Number of rows updated.
310      */
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);
316
317                 Map<String, Integer> map = new TreeMap<String, Integer>();
318                 map.put("result", new Integer(result));
319
320                 return map;
321             }
322         });
323
324         return ((Integer) results.get("result")).intValue();
325     }
326
327     /**
328      * Executes a transaction with a result.
329      * 
330      * @param aCallback
331      *            Callback to do your transactional work.
332      * @return Result.
333      */
334     public Object executeTransaction(TransactionCallback aCallback) {
335         TransactionTemplate lTemplate = new TransactionTemplate(
336                 getTransactionManager());
337         return lTemplate.execute(aCallback);
338     }
339
340     /**
341      * Executes a transaction without a result.
342      * 
343      * @param aCallback
344      *            Callback to do your transactional work. .
345      */
346     public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
347         TransactionTemplate template = new TransactionTemplate(
348                 getTransactionManager());
349         template.execute(aCallback);
350     }
351
352     /**
353      * Executes a transaction with a result, causing the testcase to fail if any
354      * type of exception is thrown.
355      * 
356      * @param aCallback
357      *            Code to be executed within the transaction.
358      * @return Result.
359      */
360     public Map executeTransaction(final TestTransactionCallback aCallback) {
361         return (Map) executeTransaction(new TransactionCallback() {
362             public Object doInTransaction(TransactionStatus aArg) {
363                 try {
364                     return aCallback.execute();
365                 } catch (Exception e) {
366                     // test case must fail.
367                     e.printStackTrace();
368                     throw new RuntimeException(e);
369                 }
370             }
371         });
372     }
373
374     /**
375      * Executes a transaction with a result, causing the testcase to fail if any
376      * type of exception is thrown.
377      * 
378      * @param aCallback
379      *            Code to be executed within the transaction.
380      */
381     public void executeTransaction(
382             final TestTransactionCallbackWithoutResult aCallback) {
383         executeTransaction(new TransactionCallbackWithoutResult() {
384             public void doInTransactionWithoutResult(TransactionStatus aArg) {
385                 try {
386                     aCallback.execute();
387                 } catch (Exception e) {
388                     // test case must fail.
389                     throw new RuntimeException(e.getMessage(), e);
390                 }
391             }
392         });
393     }
394
395     /**
396      * Executes an SQL query.
397      * 
398      * @param aSql
399      *            Query to execute.
400      * @return Result set.
401      */
402     public ResultSet executeQuery(String aSql) {
403         return executeQuery(aSql, new Object[0]);
404     }
405
406     /**
407      * Executes a query with a single argument. See
408      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
409      * supported argument types.
410      * 
411      * @param aSql
412      *            Query.
413      * @param aArg
414      *            Argument.
415      * @return Result set.
416      */
417     public ResultSet executeQuery(String aSql, Object aArg) {
418         return executeQuery(aSql, new Object[] { aArg });
419     }
420
421     /**
422      * Executes a query. See
423      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
424      * supported argument types.
425      * 
426      * @param aSql
427      *            Sql query.
428      * @param aArgs
429      *            Arguments to the query.
430      * @return Result set.
431      */
432     public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
433         try {
434             Connection connection = getConnection();
435
436             PreparedStatement statement = connection.prepareStatement(aSql);
437             setPreparedParams(aArgs, statement);
438
439             return statement.executeQuery();
440         } catch (SQLException e) {
441             throw new RuntimeException(e);
442         }
443     }
444
445     /**
446      * Sets the values of a prepared statement. See
447      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
448      * supported argument types.
449      * 
450      * @param aArgs
451      *            Arguments to the prepared statement.
452      * @param aStatement
453      *            Prepared statement
454      * @throws SQLException
455      */
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]);
460         }
461     }
462
463     /**
464      * Sets a prepared statement parameter.
465      * 
466      * @param aIndex
467      *            Index of the parameter.
468      * @param aStatement
469      *            Prepared statement.
470      * @param aObject
471      *            Value Must be of type Integer, Long, or String. TODO extend
472      *            with more types of values.
473      * @throws SQLException
474      */
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);
483         } else {
484             TestCase.fail("Unsupported object type for prepared statement: "
485                     + aObject.getClass() + " value: " + aObject
486                     + " statement: " + aStatement);
487         }
488     }
489
490     private boolean isDatabaseConfigured() {
491         try {
492             getDataSource();
493         } catch (NoSuchBeanDefinitionException e) {
494             return false;
495         }
496         return true;
497     }
498
499     /**
500      * @return Returns the dataSource.
501      */
502     public DataSource getDataSource() {
503         return _dataSource;
504     }
505
506     /**
507      * @return
508      * @throws SQLException
509      */
510     public int getTableSize(final String aTable) throws SQLException {
511
512         ResultSet resultSet = executeQuery("select * from " + aTable);
513         int count = 0;
514
515         while (resultSet.next()) {
516             count++;
517         }
518         return count;
519     }
520
521     public int countResultSet(ResultSet aResultSet) throws SQLException {
522         int count = 0;
523
524         while (aResultSet.next()) {
525             count++;
526         }
527
528         return count;
529     }
530
531 }