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