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