source code formatting.
[utils] / system / spring / src / test / java / org / wamblee / system / spring / component / 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 package org.wamblee.system.spring.component;
17
18 import junit.framework.TestCase;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22
23 import org.dbunit.DatabaseUnitException;
24
25 import org.dbunit.database.DatabaseConnection;
26 import org.dbunit.database.DatabaseSequenceFilter;
27 import org.dbunit.database.IDatabaseConnection;
28
29 import org.dbunit.dataset.FilteredDataSet;
30 import org.dbunit.dataset.IDataSet;
31 import org.dbunit.dataset.filter.ITableFilter;
32
33 import org.dbunit.operation.DatabaseOperation;
34
35 import org.hibernate.SessionFactory;
36
37 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
38
39 import org.springframework.context.ApplicationContext;
40
41 import org.springframework.jdbc.core.JdbcTemplate;
42 import org.springframework.jdbc.datasource.DataSourceUtils;
43
44 import org.springframework.orm.hibernate3.HibernateTemplate;
45
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;
53
54 import org.wamblee.test.spring.TestTransactionCallback;
55 import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
56
57 import java.sql.Connection;
58 import java.sql.PreparedStatement;
59 import java.sql.ResultSet;
60 import java.sql.SQLException;
61
62 import java.util.ArrayList;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.TreeMap;
67
68 import javax.sql.DataSource;
69
70
71 /**
72  * Test support class for database testing. Currently, this still requires
73  * the spring platform transaction manager and hibernate template.
74  */
75 public class DatabaseTesterComponent {
76     /**
77      * DOCUMENT ME!
78      */
79     private static final Log LOG = LogFactory.getLog(DatabaseTesterComponent.class);
80
81     /**
82      * Schema pattern.
83      */
84     private static final String SCHEMA_PATTERN = "%";
85
86     /**
87      * Cached spring application context.
88      */
89     private ApplicationContext context;
90
91     /**
92      * DOCUMENT ME!
93      */
94     private HibernateTemplate hibernateTemplate;
95
96     /**
97      * DOCUMENT ME!
98      */
99     private PlatformTransactionManager transactionManager;
100
101     /**
102      * DOCUMENT ME!
103      */
104     private DataSource dataSource;
105
106 /**
107      * Creates a new DatabaseTesterComponent object.
108      *
109      * @param aHibernateTemplate DOCUMENT ME!
110      * @param aTransactionManager DOCUMENT ME!
111      * @param aDataSource DOCUMENT ME!
112      */
113     public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
114         PlatformTransactionManager aTransactionManager, DataSource aDataSource) {
115         hibernateTemplate      = aHibernateTemplate;
116         transactionManager     = aTransactionManager;
117         dataSource             = aDataSource;
118     }
119
120     /**
121      * DOCUMENT ME!
122      *
123      * @return Hibernate session factory.
124      */
125     protected SessionFactory getSessionFactory() {
126         return hibernateTemplate.getSessionFactory();
127     }
128
129     /**
130      * Performs common initialization for test cases:
131      *  <ul>
132      *      <li>Cleaning the database.</li>
133      *  </ul>
134      *
135      * @throws Exception
136      */
137     public void setUp() throws Exception {
138         LOG.info("Performing setUp()");
139
140         cleanDatabase();
141     }
142
143     /**
144      * Performs common tear down after execution of a test case.
145      * Currenlty this method does nothing.
146      *
147      * @throws Exception
148      */
149     protected void tearDown() throws Exception {
150         // Empty
151     }
152
153     /**
154      * DOCUMENT ME!
155      *
156      * @return Transaction manager
157      */
158     protected PlatformTransactionManager getTransactionManager() {
159         return transactionManager;
160     }
161
162     /**
163      * DOCUMENT ME!
164      *
165      * @return Starts a new transaction.
166      */
167     protected TransactionStatus getTransaction() {
168         DefaultTransactionDefinition def = new DefaultTransactionDefinition();
169         def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
170
171         return getTransactionManager().getTransaction(def);
172     }
173
174     /**
175      * Returns the hibernate template for executing hibernate-specific
176      * functionality.
177      *
178      * @return Hibernate template.
179      */
180     public HibernateTemplate getTemplate() {
181         return hibernateTemplate;
182     }
183
184     /**
185      * Flushes the session. Should be called after some Hibernate work
186      * and before JDBC is used to check results.
187      */
188     public void flush() {
189         getTemplate().flush();
190     }
191
192     /**
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.
196      */
197     public void clear() {
198         flush();
199         getTemplate().clear();
200     }
201
202     /**
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.
207      *
208      * @param aObject
209      */
210     protected void evict(Object aObject) {
211         getTemplate().evict(aObject);
212     }
213
214     /**
215      * Gets the connection.
216      *
217      * @return Connection.
218      */
219     public Connection getConnection() {
220         return DataSourceUtils.getConnection(getDataSource());
221     }
222
223     /**
224      * DOCUMENT ME!
225      *
226      * @throws SQLException DOCUMENT ME!
227      */
228     public void cleanDatabase() throws SQLException {
229         if (!isDatabaseConfigured()) {
230             return;
231         }
232
233         String[] tables = getTableNames();
234
235         try {
236             IDatabaseConnection connection = new DatabaseConnection(getConnection());
237             ITableFilter        filter     = new DatabaseSequenceFilter(connection,
238                     tables);
239             IDataSet            dataset    = new FilteredDataSet(filter,
240                     connection.createDataSet(tables));
241
242             DatabaseOperation.DELETE_ALL.execute(connection, dataset);
243         } catch (DatabaseUnitException e) {
244             SQLException exc = new SQLException(e.getMessage());
245             exc.initCause(e);
246             throw exc;
247         }
248     }
249
250     /**
251      * DOCUMENT ME!
252      *
253      * @return DOCUMENT ME!
254      *
255      * @throws SQLException
256      */
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 + "'");
261
262         ResultSet tables = getConnection().getMetaData()
263             .getTables(null, SCHEMA_PATTERN, "%", new String[] { "TABLE" });
264
265         while (tables.next()) {
266             String table = tables.getString("TABLE_NAME");
267
268             // Make sure we do not touch hibernate's specific
269             // infrastructure tables.
270             if (!table.toLowerCase().startsWith("hibernate")) {
271                 result.add(table);
272                 LOG.debug("Adding " + table
273                     + " to list of tables to be cleaned.");
274             }
275         }
276
277         return (String[]) result.toArray(new String[0]);
278     }
279
280     /**
281      * DOCUMENT ME!
282      *
283      * @param aTableList DOCUMENT ME!
284      *
285      * @throws SQLException
286      */
287     public void emptyTables(List aTableList) throws SQLException {
288         Iterator liTable = aTableList.iterator();
289
290         while (liTable.hasNext()) {
291             emptyTable((String) liTable.next());
292         }
293     }
294
295     /**
296      * DOCUMENT ME!
297      *
298      * @param aTable DOCUMENT ME!
299      *
300      * @throws SQLException
301      */
302     public void emptyTable(String aTable) throws SQLException {
303         executeSql("delete from " + aTable);
304     }
305
306     /**
307      * DOCUMENT ME!
308      *
309      * @param aTable DOCUMENT ME!
310      *
311      * @throws SQLException
312      */
313     public void dropTable(String aTable) throws SQLException {
314         executeQuery("drop table " + aTable);
315     }
316
317     /**
318      * Executes an SQL statement within a transaction.
319      *
320      * @param aSql SQL statement.
321      *
322      * @return Return code of the corresponding JDBC call.
323      */
324     public int executeSql(final String aSql) {
325         return executeSql(aSql, new Object[0]);
326     }
327
328     /**
329      * Executes an SQL statement within a transaction. See {@link
330      * #setPreparedParam(int, PreparedStatement, Object)}for details on
331      * supported argument types.
332      *
333      * @param aSql SQL statement.
334      * @param aArg Argument of the sql statement.
335      *
336      * @return Return code of the corresponding JDBC call.
337      */
338     public int executeSql(final String aSql, final Object aArg) {
339         return executeSql(aSql, new Object[] { aArg });
340     }
341
342     /**
343      * Executes an sql statement. See {@link #setPreparedParam(int,
344      * PreparedStatement, Object)}for details on supported argument types.
345      *
346      * @param aSql SQL query to execute.
347      * @param aArgs Arguments.
348      *
349      * @return Number of rows updated.
350      */
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,
356                                 aArgs);
357
358                         Map<String, Integer> map      = new TreeMap<String, Integer>();
359                         map.put("result", new Integer(result));
360
361                         return map;
362                     }
363                 });
364
365         return ((Integer) results.get("result")).intValue();
366     }
367
368     /**
369      * Executes a transaction with a result.
370      *
371      * @param aCallback Callback to do your transactional work.
372      *
373      * @return Result.
374      */
375     public Object executeTransaction(TransactionCallback aCallback) {
376         TransactionTemplate lTemplate = new TransactionTemplate(getTransactionManager());
377
378         return lTemplate.execute(aCallback);
379     }
380
381     /**
382      * Executes a transaction without a result.
383      *
384      * @param aCallback Callback to do your transactional work. .
385      */
386     public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
387         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
388         template.execute(aCallback);
389     }
390
391     /**
392      * Executes a transaction with a result, causing the testcase to
393      * fail if any type of exception is thrown.
394      *
395      * @param aCallback Code to be executed within the transaction.
396      *
397      * @return Result.
398      *
399      * @throws RuntimeException DOCUMENT ME!
400      */
401     public Map executeTransaction(final TestTransactionCallback aCallback) {
402         return (Map) executeTransaction(new TransactionCallback() {
403                 public Object doInTransaction(TransactionStatus aArg) {
404                     try {
405                         return aCallback.execute();
406                     } catch (Exception e) {
407                         // test case must fail.
408                         e.printStackTrace();
409                         throw new RuntimeException(e);
410                     }
411                 }
412             });
413     }
414
415     /**
416      * Executes a transaction with a result, causing the testcase to
417      * fail if any type of exception is thrown.
418      *
419      * @param aCallback Code to be executed within the transaction.
420      *
421      * @throws RuntimeException DOCUMENT ME!
422      */
423     public void executeTransaction(
424         final TestTransactionCallbackWithoutResult aCallback) {
425         executeTransaction(new TransactionCallbackWithoutResult() {
426                 public void doInTransactionWithoutResult(TransactionStatus aArg) {
427                     try {
428                         aCallback.execute();
429                     } catch (Exception e) {
430                         // test case must fail.
431                         throw new RuntimeException(e.getMessage(), e);
432                     }
433                 }
434             });
435     }
436
437     /**
438      * Executes an SQL query.
439      *
440      * @param aSql Query to execute.
441      *
442      * @return Result set.
443      */
444     public ResultSet executeQuery(String aSql) {
445         return executeQuery(aSql, new Object[0]);
446     }
447
448     /**
449      * Executes a query with a single argument. See {@link
450      * #setPreparedParam(int, PreparedStatement, Object)}for details on
451      * supported argument types.
452      *
453      * @param aSql Query.
454      * @param aArg Argument.
455      *
456      * @return Result set.
457      */
458     public ResultSet executeQuery(String aSql, Object aArg) {
459         return executeQuery(aSql, new Object[] { aArg });
460     }
461
462     /**
463      * Executes a query. See {@link #setPreparedParam(int,
464      * PreparedStatement, Object)}for details on supported argument types.
465      *
466      * @param aSql Sql query.
467      * @param aArgs Arguments to the query.
468      *
469      * @return Result set.
470      *
471      * @throws RuntimeException DOCUMENT ME!
472      */
473     public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
474         try {
475             Connection        connection = getConnection();
476
477             PreparedStatement statement  = connection.prepareStatement(aSql);
478             setPreparedParams(aArgs, statement);
479
480             return statement.executeQuery();
481         } catch (SQLException e) {
482             throw new RuntimeException(e);
483         }
484     }
485
486     /**
487      * Sets the values of a prepared statement. See {@link
488      * #setPreparedParam(int, PreparedStatement, Object)}for details on
489      * supported argument types.
490      *
491      * @param aArgs Arguments to the prepared statement.
492      * @param aStatement Prepared statement
493      *
494      * @throws SQLException
495      */
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]);
500         }
501     }
502
503     /**
504      * Sets a prepared statement parameter.
505      *
506      * @param aIndex Index of the parameter.
507      * @param aStatement Prepared statement.
508      * @param aObject Value Must be of type Integer, Long, or String.
509      *
510      * @throws SQLException
511      */
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);
520         } else {
521             TestCase.fail("Unsupported object type for prepared statement: "
522                 + aObject.getClass() + " value: " + aObject + " statement: "
523                 + aStatement);
524         }
525     }
526
527     /**
528      * DOCUMENT ME!
529      *
530      * @return DOCUMENT ME!
531      */
532     private boolean isDatabaseConfigured() {
533         try {
534             getDataSource();
535         } catch (NoSuchBeanDefinitionException e) {
536             return false;
537         }
538
539         return true;
540     }
541
542     /**
543      * DOCUMENT ME!
544      *
545      * @return Returns the dataSource.
546      */
547     public DataSource getDataSource() {
548         return dataSource;
549     }
550
551     /**
552      * DOCUMENT ME!
553      *
554      * @param aTable DOCUMENT ME!
555      *
556      * @return
557      *
558      * @throws SQLException
559      */
560     public int getTableSize(final String aTable) throws SQLException {
561         ResultSet resultSet = executeQuery("select * from " + aTable);
562         int       count     = 0;
563
564         while (resultSet.next()) {
565             count++;
566         }
567
568         return count;
569     }
570
571     /**
572      * DOCUMENT ME!
573      *
574      * @param aResultSet DOCUMENT ME!
575      *
576      * @return DOCUMENT ME!
577      *
578      * @throws SQLException DOCUMENT ME!
579      */
580     public int countResultSet(ResultSet aResultSet) throws SQLException {
581         int count = 0;
582
583         while (aResultSet.next()) {
584             count++;
585         }
586
587         return count;
588     }
589 }