All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.unitils.database.DatabaseModule Maven / Gradle / Ivy

Go to download

Unitils provides utilities to further simplify unit-testing with JUnit, DBUnit, EasyMock Hibernate and Spring. The goal is to make unit-testing easy and maintainable by offering utilities such as automatic DB-schema maintainance and equality assertion through reflection.

There is a newer version: 3.4.6
Show newest version
/*
 * Copyright 2006-2007,  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.database;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.SQLHandler;
import org.unitils.database.annotations.TestDataSource;
import org.unitils.database.annotations.Transactional;
import org.unitils.database.config.DataSourceFactory;
import org.unitils.database.transaction.TransactionManager;
import org.unitils.database.transaction.TransactionManagerFactory;
import org.unitils.database.transaction.TransactionalDataSource;
import org.unitils.database.util.Flushable;
import org.unitils.database.util.TransactionMode;
import static org.unitils.database.util.TransactionMode.*;

import org.unitils.dbmaintainer.DBMaintainer;
import org.unitils.dbmaintainer.clean.DBCleaner;
import org.unitils.dbmaintainer.clean.DBClearer;
import org.unitils.dbmaintainer.structure.ConstraintsDisabler;
import org.unitils.dbmaintainer.structure.DataSetStructureGenerator;
import org.unitils.dbmaintainer.structure.SequenceUpdater;
import org.unitils.dbmaintainer.util.DatabaseModuleConfigUtils;
import org.unitils.dbmaintainer.util.DatabaseTask;

import static org.unitils.util.AnnotationUtils.*;
import static org.unitils.util.ConfigUtils.getConfiguredInstance;
import static org.unitils.util.ModuleUtils.getAnnotationPropertyDefaults;
import static org.unitils.util.ModuleUtils.getEnumValueReplaceDefault;
import org.unitils.util.PropertyUtils;
import static org.unitils.util.ReflectionUtils.setFieldAndSetterValue;

import javax.sql.DataSource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * todo add javadoc explaining transaction behavior.
 * 

* Module that provides basic support for database testing such as the creation of a datasource that connectes to the * test database and the maintaince of the test database structure. *

* A datasource will be created the first time one is requested. Which type of datasource will be created depends on * the configured {@link DataSourceFactory}. By default this will be a pooled datasource that gets its connection-url * and username and password from the unitils configuration. *

* The created datasource can be injected into a field of the test by annotating the field with {@link TestDataSource}. * It can then be used to install it in your DAO or other class under test. See the javadoc of the annotation for more info * on how you can use it. *

* If the DbMaintainer is enabled (by setting {@link #PROPKEY_UPDATEDATABASESCHEMA_ENABLED} to true), the test database * schema will automatically be updated if needed. This check will be performed once during your test-suite run, namely * when the data source is created. See {@link DBMaintainer} javadoc for more information on how this update is performed. * * @author Filip Neven * @author Tim Ducheyne */ public class DatabaseModule implements Module { /* Property indicating if the database schema should be updated before performing the tests */ public static final String PROPKEY_UPDATEDATABASESCHEMA_ENABLED = "updateDataBaseSchema.enabled"; /* The logger instance for this class */ private static Log logger = LogFactory.getLog(DatabaseModule.class); /* Map holding the default configuration of the database module annotations */ private Map, Map> defaultAnnotationPropertyValues; /* The datasource instance */ private TransactionalDataSource dataSource; /* The configuration of Unitils */ private Properties configuration; /* Indicates if the DBMaintainer should be invoked to update the database */ private boolean updateDatabaseSchemaEnabled; /* The transaction manager */ private TransactionManager transactionManager; /** * Initializes this module using the given Configuration * * @param configuration the config, not null */ public void init(Properties configuration) { this.configuration = configuration; defaultAnnotationPropertyValues = getAnnotationPropertyDefaults(DatabaseModule.class, configuration, Transactional.class); updateDatabaseSchemaEnabled = PropertyUtils.getBoolean(PROPKEY_UPDATEDATABASESCHEMA_ENABLED, configuration); } /** * Returns the DataSource that provides connection to the unit test database. When invoked the first * time, the DBMaintainer is invoked to make sure the test database is up-to-date (if database updating is enabled) * * @return The DataSource */ public TransactionalDataSource getDataSource() { if (dataSource == null) { dataSource = createDataSource(); } return dataSource; } /** * Gets the transaction manager or creates one if it does not exist yet. * * @return The transaction manager, not null */ public TransactionManager getTransactionManager() { if (transactionManager == null) { transactionManager = createTransactionManager(); } return transactionManager; } /** * Flushes all pending updates to the database. This method is useful when the effect of updates needs to * be checked directly on the database. *

* This will look for modules that implement {@link Flushable} and call flushDatabaseUpdates on these module. * @param testObject TODO */ public void flushDatabaseUpdates(Object testObject) { logger.info("Flushing database updates."); List flushables = Unitils.getInstance().getModulesRepository().getModulesOfType(Flushable.class); for (Flushable flushable : flushables) { flushable.flushDatabaseUpdates(testObject); } } /** * Determines whether the test database is outdated and, if that is the case, updates the database with the * latest changes. See {@link DBMaintainer} for more information. */ public void updateDatabase() { updateDatabase(getDefaultSqlHandler()); } /** * todo make configurable using properties *

* Determines whether the test database is outdated and, if that is the case, updates the database with the * latest changes. See {@link DBMaintainer} for more information. * * @param sqlHandler SQLHandler that needs to be used for the database updates */ public void updateDatabase(SQLHandler sqlHandler) { try { logger.info("Checking if database has to be updated."); DBMaintainer dbMaintainer = new DBMaintainer(configuration, sqlHandler); dbMaintainer.updateDatabase(); } catch (UnitilsException e) { throw new UnitilsException("Error while updating database", e); } } /** * Updates the database version to the current version, without issuing any other updates to the database. * This method can be used for example after you've manually brought the database to the latest version, but * the database version is not yet set to the current one. This method can also be useful for example for * reinitializing the database after having reorganized the scripts folder. */ public void setDatabaseToCurrentVersion() { setDatabaseToCurrentVersion(getDefaultSqlHandler()); } /** * Updates the database version to the current version, without issuing any other updates to the database. * This method can be used for example after you've manually brought the database to the latest version, but * the database version is not yet set to the current one. This method can also be useful for example for * reinitializing the database after having reorganized the scripts folder. * * @param sqlHandler The {@link SQLHandler} to which all commands are issued */ public void setDatabaseToCurrentVersion(SQLHandler sqlHandler) { DBMaintainer dbMaintainer = new DBMaintainer(configuration, sqlHandler); dbMaintainer.setDatabaseToCurrentVersion(); } /** * Assigns the TestDataSource to every field annotated with {@link TestDataSource} and calls all methods * annotated with {@link TestDataSource} * * @param testObject The test instance, not null */ public void injectDataSource(Object testObject) { List fields = getFieldsAnnotatedWith(testObject.getClass(), TestDataSource.class); List methods = getMethodsAnnotatedWith(testObject.getClass(), TestDataSource.class); if (fields.isEmpty() && methods.isEmpty()) { // Nothing to do. Jump out to make sure that we don't try to instantiate the DataSource return; } setFieldAndSetterValue(testObject, fields, methods, getDataSource()); } /** * Creates a datasource by using the factory that is defined by the dataSourceFactory.className property * * @return the datasource */ protected TransactionalDataSource createDataSource() { // Get the factory for the data source and create it DataSourceFactory dataSourceFactory = getConfiguredInstance(DataSourceFactory.class, configuration); dataSourceFactory.init(configuration); DataSource dataSource = dataSourceFactory.createDataSource(); // Make data source transactional TransactionalDataSource transactionalDataSource = getTransactionManager().createTransactionalDataSource(dataSource); // Call the database maintainer if enabled if (updateDatabaseSchemaEnabled) { updateDatabase(new SQLHandler(transactionalDataSource)); } return transactionalDataSource; } /** * @return An instance of the transactionManager, as configured in the Unitils configuration */ protected TransactionManager createTransactionManager() { // Get the factory for the transaction manager TransactionManagerFactory transactionManagerFactory = getConfiguredInstance(TransactionManagerFactory.class, configuration); transactionManagerFactory.init(configuration); return transactionManagerFactory.createTransactionManager(); } /** * @param testObject The test object, not null * @return The {@link TransactionMode} for the given object */ protected TransactionMode getTransactionMode(Object testObject) { TransactionMode transactionMode = getClassLevelAnnotationProperty(Transactional.class, "value", DEFAULT, testObject.getClass()); transactionMode = getEnumValueReplaceDefault(Transactional.class, "value", transactionMode, defaultAnnotationPropertyValues); return transactionMode; } /** * Starts a transaction. If the Unitils DataSource was not loaded yet, we simply remember that a * transaction was started but don't actually start it. If the DataSource is loaded within this * test, the transaction will be started immediately after loading the DataSource. * * @param testObject The test object, not null */ public void startTransaction(Object testObject) { TransactionMode transactionMode = getTransactionMode(testObject); if (transactionMode == DISABLED) { return; } getTransactionManager().startTransaction(testObject); } /** * Commits or rollbacks the current transaction, if transactions are enabled and a transactionManager is * active for the given testObject * * @param testObject The test object, not null */ protected void endTransaction(Object testObject) { TransactionMode transactionMode = getTransactionMode(testObject); if (transactionMode == DISABLED) { return; } TransactionManager transactionManager = getTransactionManager(); if (transactionMode == COMMIT) { transactionManager.commit(testObject); } else if (getTransactionMode(testObject) == ROLLBACK) { transactionManager.rollback(testObject); } } /** * Clears all configured schema's. I.e. drops all tables, views and other database objects. */ public void clearSchemas() { getConfiguredDatabaseTaskInstance(DBClearer.class).clearSchemas(); } /** * Cleans all configured schema's. I.e. removes all data from its database tables. */ public void cleanSchemas() { getConfiguredDatabaseTaskInstance(DBCleaner.class).cleanSchemas(); } /** * Disables all foreigh key and not-null constraints on the configured schema's. */ public void disableConstraints() { getConfiguredDatabaseTaskInstance(ConstraintsDisabler.class).disableConstraints(); } /** * Updates all sequences that have a value below a certain configurable treshold to become equal * to this treshold */ public void updateSequences() { getConfiguredDatabaseTaskInstance(SequenceUpdater.class).updateSequences(); } /** * Generates a definition file that defines the structure of dataset's, i.e. a XSD of DTD that * describes the structure of the database. */ public void generateDatasetDefinition() { getConfiguredDatabaseTaskInstance(DataSetStructureGenerator.class).generateDataSetStructure(); } /** * @return A configured instance of {@link DatabaseTask} of the given type */ private T getConfiguredDatabaseTaskInstance(Class databaseTaskType) { return DatabaseModuleConfigUtils.getConfiguredDatabaseTaskInstance(databaseTaskType, configuration, getDefaultSqlHandler()); } /** * @return The default SQLHandler, which simply executes the sql statements on the unitils-configured * test database */ protected SQLHandler getDefaultSqlHandler() { return new SQLHandler(getDataSource()); } /** * @return The {@link TestListener} associated with this module */ public TestListener createTestListener() { return new DatabaseTestListener(); } /** * The {@link TestListener} for this module */ protected class DatabaseTestListener extends TestListener { @Override public void beforeTestSetUp(Object testObject) { injectDataSource(testObject); startTransaction(testObject); } @Override public void afterTestTearDown(Object testObject) { endTransaction(testObject); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy