/*
* Copyright 2008, Unitils.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package org.unitils.dbunit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dbunit.database.DatabaseConfig;
import static org.dbunit.database.DatabaseConfig.*;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.ext.mysql.MySqlMetadataHandler;
import org.unitils.core.Module;
import org.unitils.core.TestListener;
import org.unitils.core.Unitils;
import org.unitils.core.UnitilsException;
import org.unitils.core.dbsupport.DbSupport;
import org.unitils.core.dbsupport.DbSupportFactory;
import static org.unitils.core.dbsupport.DbSupportFactory.getDbSupport;
import org.unitils.core.dbsupport.DefaultSQLHandler;
import org.unitils.core.dbsupport.SQLHandler;
import org.unitils.core.util.ConfigUtils;
import static org.unitils.core.util.ConfigUtils.getInstanceOf;
import org.unitils.database.DatabaseModule;
import org.unitils.dbunit.annotation.DataSet;
import org.unitils.dbunit.annotation.ExpectedDataSet;
import org.unitils.dbunit.datasetfactory.DataSetFactory;
import org.unitils.dbunit.datasetfactory.DataSetResolver;
import org.unitils.dbunit.datasetloadstrategy.DataSetLoadStrategy;
import org.unitils.dbunit.util.DataSetAssert;
import org.unitils.dbunit.util.DbUnitDatabaseConnection;
import org.unitils.dbunit.util.MultiSchemaDataSet;
import static org.unitils.util.AnnotationUtils.getMethodOrClassLevelAnnotation;
import static org.unitils.util.AnnotationUtils.getMethodOrClassLevelAnnotationProperty;
import static org.unitils.util.ModuleUtils.*;
import static org.unitils.util.ReflectionUtils.createInstanceOfType;
import static org.unitils.util.ReflectionUtils.getClassWithName;
import javax.sql.DataSource;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;
/**
* Module that provides support for managing database test data using DBUnit.
* <p/>
* Loading of DbUnit data sets can be done by annotating a class or method with the {@link DataSet} annotation. The name
* of the data set files can be specified explicitly as an argument of the annotation. If no file name is specified, it looks
* for a file in the same directory as the test class named: 'classname without packagename'.xml.
* <p/>
* By annotating a method with the {@link ExpectedDataSet} annotation or by calling the {@link #assertDbContentAsExpected}
* method, the contents of the database can be compared with the contents of a dataset. The expected dataset can be
* passed as an argument of the annotation. If no file name is specified it looks for a file in the same directory
* as the test class that has following name: 'classname without packagename'.'test method name'-result.xml.
* <p/>
* This module depends on the {@link DatabaseModule} for database connection management.
*
* @author Filip Neven
* @author Tim Ducheyne
*/
public class DbUnitModule implements Module {
/* The logger instance for this class */
private static Log logger = LogFactory.getLog(DbUnitModule.class);
/**
* Map holding the default configuration of the dbunit module annotations
*/
protected Map<Class<? extends Annotation>, Map<String, String>> defaultAnnotationPropertyValues;
/**
* Objects that DbUnit uses to connect to the database and to cache some database metadata. Since DBUnit's data
* caching is time-consuming, this object is created only once and used throughout the entire test run. The
* underlying JDBC Connection however is 'closed' (returned to the pool) after every database operation.
* <p/>
* A different DbUnit connection is used for every database schema. Since DbUnit can only work with a single schema,
* this is the simplest way to obtain multi-schema support.
*/
protected Map<String, DbUnitDatabaseConnection> dbUnitDatabaseConnections = new HashMap<String, DbUnitDatabaseConnection>();
/**
* The unitils configuration
*/
protected Properties configuration;
/**
* Initializes the DbUnitModule using the given Configuration
*
* @param configuration The config, not null
*/
@SuppressWarnings("unchecked")
public void init(Properties configuration) {
this.configuration = configuration;
defaultAnnotationPropertyValues = getAnnotationPropertyDefaults(DbUnitModule.class, configuration, DataSet.class, ExpectedDataSet.class);
}
/**
* No after initialization needed for this module
*/
public void afterInit() {
}
/**
* Gets the DbUnit connection or creates one if it does not exist yet.
*
* @param schemaName The schema name, not null
* @return The DbUnit connection, not null
*/
public DbUnitDatabaseConnection getDbUnitDatabaseConnection(String schemaName) {
DbUnitDatabaseConnection dbUnitDatabaseConnection = dbUnitDatabaseConnections.get(schemaName);
if (dbUnitDatabaseConnection == null) {
dbUnitDatabaseConnection = createDbUnitConnection(schemaName);
DatabaseConfig config = dbUnitDatabaseConnection.getConfig();
dbUnitDatabaseConnections.put(schemaName, dbUnitDatabaseConnection);
}
return dbUnitDatabaseConnection;
}
/**
* This method will first try to load a method level defined dataset. If no such file exists, a class level defined
* dataset will be loaded. If neither of these files exist, nothing is done.
* The name of the test data file at both method level and class level can be overridden using the
* {@link DataSet} annotation. If specified using this annotation but not found, a {@link UnitilsException} is
* thrown.
*
* @param testMethod The method, not null
* @param testObject The test object, not null
*/
public void insertDataSet(Method testMethod, Object testObject) {
try {
MultiSchemaDataSet multiSchemaDataSet = getDataSet(testMethod, testObject);
if (multiSchemaDataSet == null) {
// no dataset specified
return;
}
DataSetLoadStrategy dataSetLoadStrategy = getDataSetLoadStrategy(testMethod, testObject.getClass());
insertDataSet(multiSchemaDataSet, dataSetLoadStrategy);
} catch (Exception e) {
throw new UnitilsException("Error inserting test data from DbUnit dataset for method " + testMethod, e);
} finally {
closeJdbcConnection();
}
}
/**
* Inserts the default dataset for the given test class into the database
*
* @param testClass The test class for which the default dataset must be loaded
*/
public void insertDefaultDataSet(Class<?> testClass) {
DataSetFactory dataSetFactory = getDefaultDataSetFactory();
String[] dataSetFileNames = new String[]{getDefaultDataSetFileName(testClass, dataSetFactory.getDataSetFileExtension())};
insertDataSet(testClass, dataSetFileNames);
}
/**
* Inserts the dataset consisting of the given list of files into the database
*
* @param testClass The test class for which the dataset must be loaded
* @param dataSetFileNames The names of the files that define the test data
*/
public void insert