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