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