/* * Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The * University of Hong Kong (HKU). All Rights Reserved. * * This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1] * * [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt */ package hk.hku.cecid.piazza.commons.test; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.lang.reflect.Type; import java.lang.reflect.ParameterizedType; import java.net.URL; import org.junit.After; import org.junit.Assert; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import hk.hku.cecid.piazza.commons.dao.DAOException; import hk.hku.cecid.piazza.commons.dao.DAOFactory; import hk.hku.cecid.piazza.commons.dao.ds.DataSourceDAO; import hk.hku.cecid.piazza.commons.dao.ds.DataSourceProcess; import hk.hku.cecid.piazza.commons.dao.ds.DataSourceTransaction; import hk.hku.cecid.piazza.commons.io.IOHandler; import hk.hku.cecid.piazza.commons.module.Module; import hk.hku.cecid.piazza.commons.util.Logger; /** * The <code>DAOTest</code> is base class for testing DAO. * * @author Twinsen Tsang * @version 1.0.0 * @since JDK5.0, H2O 0908 * * @param <T> The class which implements DataSourceDAO. * * @see DataSourceDAO */ public abstract class DAOTest<T extends DataSourceDAO> extends UnitTest<T> { protected Module container; protected Logger containerLogger; protected DAOFactory daoFactory; /* The suffix of the predefined fixture name */ public static final String CREATE_SQL_SUFFIX = ".create.sql"; public static final String DROP_SQL_SUFFIX = ".drop.sql"; public static final String INSERT_SQL_SUFFIX = ".insert.sql"; public static final String MODULE_XML_DESCRIPTOR_SUFFIX = ".module.mock.xml"; /** * Create an instance of <code>DAOTest</code>. By default, it disable JMOCK features for reducing dependency. */ public DAOTest() { super(false); } /** * Create an instance of <code>DAOTest</code>. * * @param noMocking the flag representing the test requires object mocking or not ? */ public DAOTest(boolean noMocking) { super(noMocking); } // The boolean flag indicating weather the DB is successfully created. private boolean created = false; /** * Return the DB table name accessed by this DAO. * * @return the DB table name accessed by this DAO. */ public abstract String getTableName(); /** * * @return */ public Module getTestContainer() { if (this.container == null) { throw new IllegalStateException("The container has not been set?, forget to invoke method setUp()?"); } return this.container; } /** * Initialize the test target for this test-case. * <br/><br/> * A Special piazza common module is constructed during this initialization. We will * call the module as a container of our test-case because the <code>DAO</code> * can only be constructed through <code>DAOFactory</code>. * <br/><br/> * Moreover, we have to create the database table and insert some SQL for testing * because of non-persistence database has been adopted for performing unit-testing. */ @SuppressWarnings("unchecked") public synchronized void initTestTarget() throws Exception { // Get the DAO parameter type. Class searchClass = this.getClass(); Class actualDAOClass = null; while (true) { Type type = searchClass.getGenericSuperclass(); Class typeClass = type.getClass(); if (ParameterizedType.class.isAssignableFrom(typeClass)) { Type [] allTypes = ((ParameterizedType)type).getActualTypeArguments(); if (allTypes.length < 1) { throw new IllegalArgumentException("Missing DVO type in the generic parameter type."); } else { actualDAOClass = (Class)allTypes[0]; break; } } else if (Class.class.isAssignableFrom(typeClass)) { searchClass = (Class) type; } } logger.info("Using DAO-class : {}", actualDAOClass); this.container = this.createDAOContainer(); this.daoFactory = (DAOFactory) this.container.getComponent("daofactory"); this.target = (T) this.daoFactory.createDAO(actualDAOClass); if (this.container.getLogger() != null) { this.containerLogger = this.container.getLogger(); } // Create the table. this.commitSQL(this.getTableName() + CREATE_SQL_SUFFIX); // Insert some sample data. this.commitSQL(this.getTableName() + INSERT_SQL_SUFFIX); this.created = true; } /** * This is the factory to create the DAO container (typically it is a common module). * Sub-class may override this to customize the DAO container. */ public Module createDAOContainer() throws Exception { String tableName = this.getTableName(); if (tableName == null || tableName.equals("")) { throw new NullPointerException("You must return the table name from 'getTableName()'."); } // Get the XML descriptor from resource. String resourceName = tableName + MODULE_XML_DESCRIPTOR_SUFFIX; URL xmlDescriptorURL = FIXTURE_LOADER.getResource(resourceName); String xmlDescriptor = null; if (xmlDescriptorURL == null) { throw new NullPointerException("Unable to load module descriptor '" + resourceName + "' in the classpath."); } xmlDescriptor = xmlDescriptorURL.getFile(); // Start the container. return new Module(xmlDescriptor, FIXTURE_LOADER, true); } /** * This is a helper function for commit SQL from Fixture. */ protected void commitSQL(String fixtureName) throws Exception { // Get the create table SQL from file. URL resourceURL = FIXTURE_LOADER.getResource(fixtureName); if (resourceURL == null) { this.containerLogger.warn("Unable to search '" + fixtureName + "' in the classpath"); return; } InputStream resourceStream = resourceURL.openStream(); // Read the SQL. final String sql = IOHandler.readString(resourceStream, null); final String canonicalizedSql = sql.replace("(?! \\S)\\s+", " "); this.containerLogger.info("Execute SQL: \n " + canonicalizedSql); DataSourceProcess process = new DataSourceProcess(this.target) { protected void doTransaction(DataSourceTransaction tx) throws DAOException { Statement stmt = null; try { Connection conn = tx.getConnection(); stmt = conn.createStatement(); Assert.assertThat(stmt.executeUpdate(sql), not(is(-1))); } catch(SQLException sqlex) { throw new DAOException(sqlex); // re-throw. } finally { if (stmt != null) { try { stmt.close(); } catch(SQLException sqlex) { containerLogger.error("Unable to close statement", sqlex); } } } } }; process.start(); } @Override public void tearDown() throws Exception { super.tearDown(); this.dropTable(); } /** * Drop the table for next test-case. */ public synchronized void dropTable() throws Exception { if (this.created) { try { String tableName = this.getTableName(); this.commitSQL(tableName + DROP_SQL_SUFFIX); } catch(DAOException daoex) { logger.error("Unable to tearDown the {} DB table", this.getTableName()); throw daoex; } } } }