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