/* * #%L * Nazgul Project: nazgul-core-persistence-test * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% * */ package se.jguru.nazgul.test.persistence; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.DataSetException; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.operation.DatabaseOperation; import org.junit.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jguru.nazgul.core.algorithms.api.Validate; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.io.File; import java.io.InputStream; import java.io.StringWriter; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Arrays; import java.util.List; import java.util.Map; /** * Abstract superclass for JPA based tests, that uses dbUnit to define * criteria to setup and evaluate database state * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public abstract class AbstractDbUnitAndJpaTest extends AbstractJpaTest { // Our Log private static final Logger log = LoggerFactory.getLogger(AbstractDbUnitAndJpaTest.class); /** * The OpenJpa username property. */ public static final String OPENJPA_CONNECTION_USERNAME_KEY = "openjpa.ConnectionUserName"; /** * The OpenJpa password property. */ public static final String OPENJPA_CONNECTION_PASSWORD_KEY = "openjpa.ConnectionPassword"; /** * The dbUnit IDatabaseConnection, hooked up to the same * database as the JPA EntityManager, either using a connection of its own * or piggybacking on */ protected IDatabaseConnection iDatabaseConnection; /** * Retrieves the name of the database used for this AbstractDbUnitAndJpaTest. * * @return the name of the database used for this AbstractDbUnitAndJpaTest. */ protected abstract String getDatabaseName(); /** * @return The DatabaseType used by this AbstractJpaPersistenceDbUnitTest. */ protected abstract DatabaseType getDatabaseType(); // Internal state /** * Retrieves {@code true} if dbUnit and JPA should use separate Connections * to the test database, and {@code false} otherwise. Defaults to {@code false}, * but can be overridden in subclasses to use other behaviour. * * @return {@code true} if dbUnit and JPA should use separate Connections * to the test database, and {@code false} otherwise. */ protected boolean isSeparateConnectionsUsedForDbUnitAndJPA() { return false; } /** * Setting up the DbUnit framework which setup the inherited Persistence Entity Manager. * DbUnit will use the already initiated SQL connection from the Persistence Entity Manager. * * @throws Exception if an error occurred. */ @Before @SuppressWarnings("PMD.CloseResource") public void setUp() throws Exception { // Delegate super.setUp(); // Acquire the jUnit DatabaseConnection. getDatabaseConnection(false); } /** * Acquires the dbUnit IDatabaseConnection, and overwrites the local/protected iDatabaseConnection member * with the result. * * @param newConnection if {@code true}, the IDatabaseConnection will be re-acquired from the * underlying JDBC Connection even if it exists. * @return The newly acquired IDatabaseConnection. * @throws Exception if the underlying dbUnit DatabaseConnection could not be created. */ @SuppressWarnings("PMD") protected final IDatabaseConnection getDatabaseConnection(final boolean newConnection) throws Exception { if (iDatabaseConnection == null || newConnection) { // Acquire the Connection to the database final Connection dbConnection = isSeparateConnectionsUsedForDbUnitAndJPA() ? getStandaloneDbConnection(getDatabaseName(), getTargetDirectory()) : getJpaUnitTestConnection(true); // Create dbUnit connection and assign the DataTypeFactory iDatabaseConnection = new DatabaseConnection(dbConnection); iDatabaseConnection.getConfig().setProperty( DatabaseConfig.PROPERTY_DATATYPE_FACTORY, getDatabaseType().getDataTypeFactory()); } // All done. return iDatabaseConnection; } /** * Retrieves a File to the target directory. * * @return the project target directory path, wrapped in a File object. */ protected File getTargetDirectory() { // Use CodeSource final URL location = getClass().getProtectionDomain().getCodeSource().getLocation(); // Check sanity if (location == null) { throw new NullPointerException("CodeSource location not found for class [" + getClass().getSimpleName() + "]"); } // All done. return new File(location.getPath()).getParentFile(); } /** * Load a DataSet from XML file. * * @param filename data file xml. * @return an IDataSet instance of the entities loaded from xml. * @throws DataSetException if an error occurred while reading XML definition. */ protected IDataSet getDataSet(@NotNull @Size(min = 1) final String filename) throws DataSetException { // Check sanity Validate.notEmpty(filename, "filename"); final FlatXmlDataSetBuilder flatXmlDataSetBuilder = new FlatXmlDataSetBuilder(); flatXmlDataSetBuilder.setColumnSensing(true); final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); final InputStream flatXmlFile = contextClassLoader.getResourceAsStream(filename); if (flatXmlFile == null) { throw new NullPointerException("Could not find a file from filename [" + filename + "]"); } // All done. return flatXmlDataSetBuilder.build(flatXmlFile); } /** * Converts the supplied dataSet to a FlatXmlDataSet which, in turn, is written to a String. * * @param dataSet The IDataSet to convert. * @return The string representation of the supplied IDataSet. */ protected final String extractFlatXmlDataSet(@NotNull final IDataSet dataSet) { // Check sanity Validate.notNull(dataSet, "dataset"); // Convert and return. final StringWriter out = new StringWriter(); try { FlatXmlDataSet.write(dataSet, out); } catch (Exception e) { throw new IllegalArgumentException("Could not write FlatXmlDataSet.", e); } // All done. return out.toString(); } /** * Drops all the Database Objects in the public schema of the active database. * * @param shutdownDatabase if {@code true}, the database should be shutdown after cleaning the schema. */ @SuppressWarnings("PMD") protected final void dropAllDbObjectsInPublicSchema(final boolean shutdownDatabase) { try { // Get a dataSet for the entire database final IDataSet dataSet = iDatabaseConnection.createDataSet(); // Delete everything in the database. DatabaseOperation.DELETE_ALL.execute(iDatabaseConnection, dataSet); } catch (Exception e) { throw new IllegalStateException("Could not delete all db objects.", e); } // TODO: Handle DB shutdown. /* ResultSet dbObjects = null; Statement dropStatement = null; EntityTransaction currentTransaction = null; if (entityManager != null) { try { currentTransaction = entityManager.getTransaction(); if(!currentTransaction.isActive()) { // Eclipselink requires an active transaction in order to get the Database Connection. currentTransaction.begin(); } // In some cases when running on Windows machines, the jpaUnitTestConnection can become null here. // Handle that case. final Connection localJpaUnitTestConnection = getJpaUnitTestConnection(true); // Revert to plain-old JDBC to drop all DB objects in the public schema. final DatabaseMetaData metaData = localJpaUnitTestConnection.getMetaData(); dbObjects = metaData.getTables(null, getDatabaseType().getPublicSchemaName(), "%", null); dropStatement = localJpaUnitTestConnection.createStatement(); while (dbObjects.next()) { final String schemaAndTableName = dbObjects.getString(2) + "." + dbObjects.getString(3); final String dbObjectType = dbObjects.getString(4); log.debug(" Dropping [" + schemaAndTableName + "] ... "); // Add the drop statement to the batch. dropStatement.addBatch("DROP " + dbObjectType + " " + schemaAndTableName + " CASCADE "); } if(shutdownDatabase) { dropStatement.addBatch("SHUTDOWN"); } final int[] results = dropStatement.executeBatch(); log.debug(" ... Done dropping [" + results.length + "] table(s)."); } catch (SQLException e) { e.printStackTrace(); } finally { if(currentTransaction != null) { try { currentTransaction.commit(); } catch (Exception e) { log.warn("Caught exception when cleaning up unit test database objects. " + "Ignoring this and proceeding, as we are about to tear down the unit test database."); } } try { if (dropStatement != null) { dropStatement.close(); } if (dbObjects != null) { dbObjects.close(); } } catch (SQLException e) { log.error("Could not close DB resource", e); } } } */ } // // Private helpers // private Connection getStandaloneDbConnection(final String databaseName, final File targetDirectory) { final List<ClassLoader> classLoaders = Arrays.asList(Thread.currentThread().getContextClassLoader(), getClass().getClassLoader()); final String jdbcDriverClass = getDatabaseType().getJdbcDriverClass(); // 0) Load the DB Driver ClassNotFoundException classNotFoundException = null; for (ClassLoader current : classLoaders) { try { current.loadClass(jdbcDriverClass); if (log.isDebugEnabled()) { String classLoader = current == Thread.currentThread().getContextClassLoader() ? "ThreadLocal Context ClassLoader" : "AbstractDbUnitAndJpaTest class ClassLoader"; log.debug("Loaded JDBC driver class using " + classLoader); } // The current classloader found the JDBC driver. // Erase error state and continue. classNotFoundException = null; break; } catch (ClassNotFoundException e) { classNotFoundException = e; } } if (classNotFoundException != null) { throw new IllegalStateException("Could not load JDBC driver class '" + jdbcDriverClass + "'", classNotFoundException); } // 1) Use the standard DriverManager-based Connection final Map<String, String> properties = getEntityManagerFactoryProperties(); final String userName = properties.containsKey(OPENJPA_CONNECTION_USERNAME_KEY) ? properties.get(OPENJPA_CONNECTION_USERNAME_KEY) : "sa"; final String password = properties.containsKey(OPENJPA_CONNECTION_PASSWORD_KEY) ? properties.get(OPENJPA_CONNECTION_PASSWORD_KEY) : ""; try { final String unitTestJdbcURL = getDatabaseType().getUnitTestJdbcURL(databaseName, targetDirectory); // All done. return DriverManager.getConnection( unitTestJdbcURL, userName, password); } catch (SQLException e) { throw new IllegalStateException("Could not open JDBC Connection using [" + jdbcDriverClass + "]", e); } } }