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