package com.allanditzel.dashboard.test.dbunit; import org.dbunit.DatabaseUnitException; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.datatype.DefaultDataTypeFactory; import org.dbunit.dataset.datatype.IDataTypeFactory; import org.dbunit.ext.h2.H2DataTypeFactory; import org.dbunit.ext.hsqldb.HsqldbDataTypeFactory; import org.dbunit.ext.mssql.MsSqlDataTypeFactory; import org.dbunit.ext.mysql.MySqlDataTypeFactory; import org.dbunit.ext.oracle.Oracle10DataTypeFactory; import org.dbunit.ext.oracle.OracleDataTypeFactory; import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; /** * Adapts between DbUnit's {@code IDatabaseConnection} and a standard Java {@code DataSource}. * <p/> * To facilitate DbUnit's use of the connected database, this factory applies Hibernate-like logic for attempting to * use the {@code DatabaseMetaData} to determine which DbUnit {@code IDataTypeFactory} it should use. If an explicit * mapping from the connected database to a factory is not available, the {@code DefaultDataTypeFactory} will be used * automatically. * * @author Bryan Turner * @since 1.0 */ public class DatabaseConnectionFactory { private static final Logger log = LoggerFactory.getLogger(DatabaseConnectionFactory.class); private final DataSource dataSource; private volatile IDataTypeFactory dataTypeFactory; public DatabaseConnectionFactory(DataSource dataSource) { this.dataSource = dataSource; } /** * Constructs a new DbUnit {@code DatabaseConnection}, configures its {@code IDataTypeFactory} and returns it. * <p/> * If a Spring transaction is in progress, this should automatically use the same connection. See the wiring for * this bean in {@code test-context.xml} for more on how that works. * * @return a new DbUnit connection * @throws DatabaseUnitException if a new {@code DatabaseConnection} cannot be constructed from the JDBC connection * @throws java.sql.SQLException if a JDBC connection cannot be opened, or {@code DatabaseMetaData} cannot be * retrieved from it * @see #resolveDataTypeFactory(java.sql.Connection) */ public IDatabaseConnection newConnection() throws DatabaseUnitException, SQLException { Connection jdbcConnection = dataSource.getConnection(); if (dataTypeFactory == null) { //Not synchronised because a) unit tests tend to be serial and b) it should compute the same one anyway dataTypeFactory = resolveDataTypeFactory(jdbcConnection); } String schema = null; if (dataTypeFactory instanceof OracleDataTypeFactory) { //In Oracle, the schema name is the username. To speed up table detection in DbUnit, and also to ensure //the tests run correctly on users with high permissions, we want to only look in the user's schema. If //the user has high permissions, they start seeing SYS and SYSTEM schema tables, among others, as well //as their own, which can cause strange DbUnit failures. String userName = jdbcConnection.getMetaData().getUserName(); schema = userName == null ? null : userName.toUpperCase(); } DatabaseConnection connection = new DatabaseConnection(jdbcConnection, schema); DatabaseConfig configuration = connection.getConfig(); configuration.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, dataTypeFactory); //configuration.setProperty(DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES, Boolean.TRUE); return connection; } /** * Uses the {@code DatabaseMetaData.getDatabaseProductName()}, and optionally the major version, to try and * determine the correct DbUnit {@code IDataTypeFactory} to use. * <p/> * If the database name does not match any of the heuristics applied, a {@code DefaultDataTypeFactory} is returned * and a warning is logged. Depending on the database, the default data type factory may result in test failures. * * @param connection a JDBC connection to obtain {@code DatabaseMetaData} * @return the data type factory to use, which will never be {@code null} * @throws java.sql.SQLException if metadata cannot be retrieved */ private IDataTypeFactory resolveDataTypeFactory(Connection connection) throws SQLException { DatabaseMetaData metaData = connection.getMetaData(); String databaseName = metaData.getDatabaseProductName(); //The structure for this is drawn from Hibernate's StandardDialectResolver. I figure if these checks are robust //enough for Hibernate, they should be more than robust enough for our unit tests. IDataTypeFactory factory; if ("HSQL Database Engine".equals(databaseName)) { log.debug("Using HSQL DataTypeFactory"); factory = new HsqldbDataTypeFactory(); } else if ("H2".equals(databaseName)) { log.debug("Using H2 DataTypeFactory"); factory = new H2DataTypeFactory(); } else if ("MySQL".equals(databaseName)) { log.debug("Using MySQL DataTypeFactory"); factory = new MySqlDataTypeFactory(); } else if ("PostgreSQL".equals(databaseName)) { log.debug("Using Postgres DataTypeFactory"); factory = new PostgresqlDataTypeFactory(); } else if (databaseName.startsWith("Microsoft SQL Server")) { log.debug("Using SQL Server DataTypeFactory"); factory = new MsSqlDataTypeFactory(); } else if ("Oracle".equals(databaseName)) { if (metaData.getDatabaseMajorVersion() < 10) { log.debug("Using Oracle DataTypeFactory for 10g and later"); factory = new OracleDataTypeFactory(); } else { log.debug("Using Oracle DataTypeFactory for 9i and earlier"); factory = new Oracle10DataTypeFactory(); } } else { log.warn("No IDataTypeFactory was resolved for {}. Using default DataTypeFactory. This may result in " + "test failures. If so, please update {} with an explicit DataTypeFactory for this database.", databaseName, getClass()); return new DefaultDataTypeFactory(); } return factory; } }