added spring subsystem implementation.
[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         }
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.
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. 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         try {
494                         Connection connection = getConnection();
495
496                         PreparedStatement statement = connection.prepareStatement(aSql);
497                         setPreparedParams(aArgs, statement);
498
499                         return statement.executeQuery();
500                 } catch (SQLException e) {
501                         throw new RuntimeException(e);
502                 }
503     }
504
505     /**
506      * Sets the values of a prepared statement. See
507      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
508      * supported argument types.
509      * 
510      * @param aArgs
511      *            Arguments to the prepared statement.
512      * @param aStatement
513      *            Prepared statement
514      * @throws SQLException
515      */
516     private void setPreparedParams(final Object[] aArgs,
517             PreparedStatement aStatement) throws SQLException {
518         for (int i = 1; i <= aArgs.length; i++) {
519             setPreparedParam(i, aStatement, aArgs[i - 1]);
520         }
521     }
522
523     /**
524      * Sets a prepared statement parameter.
525      * 
526      * @param aIndex
527      *            Index of the parameter.
528      * @param aStatement
529      *            Prepared statement.
530      * @param aObject
531      *            Value Must be of type Integer, Long, or String. TODO extend
532      *            with more types of values.
533      * @throws SQLException
534      */
535     private void setPreparedParam(int aIndex, PreparedStatement aStatement,
536             Object aObject) throws SQLException {
537         if (aObject instanceof Integer) {
538             aStatement.setInt(aIndex, ((Integer) aObject).intValue());
539         } else if (aObject instanceof Long) {
540             aStatement.setLong(aIndex, ((Integer) aObject).longValue());
541         } else if (aObject instanceof String) {
542             aStatement.setString(aIndex, (String) aObject);
543         } else {
544             TestCase.fail("Unsupported object type for prepared statement: "
545                     + aObject.getClass() + " value: " + aObject
546                     + " statement: " + aStatement);
547         }
548     }
549
550     private boolean isDatabaseConfigured() {
551         try {
552             getDataSource();
553         } catch (NoSuchBeanDefinitionException e) {
554             return false;
555         }
556         return true;
557     }
558
559     /**
560      * @return Returns the dataSource.
561      */
562     public DataSource getDataSource() {
563         DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(
564                 DATA_SOURCE);
565         assertNotNull(ds);
566         return ds;
567     }
568
569     /**
570      * @return
571      * @throws SQLException
572      */
573     protected int getTableSize(final String aTable) throws SQLException {
574
575         ResultSet resultSet = executeQuery("select * from " + aTable);
576         int count = 0;
577
578         while (resultSet.next()) {
579             count++;
580         }
581         return count;
582     }
583
584     protected int countResultSet(ResultSet aResultSet) throws SQLException {
585         int count = 0;
586
587         while (aResultSet.next()) {
588             count++;
589         }
590
591         return count;
592     }
593
594 }