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

org.unitils.dbmaintainer.DBMaintainer Maven / Gradle / Ivy

/*
 * 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.dbmaintainer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.unitils.core.UnitilsException;
import org.unitils.core.dbsupport.SQLHandler;
import org.unitils.core.util.ConfigUtils;
import org.unitils.dbmaintainer.clean.DBCleaner;
import org.unitils.dbmaintainer.clean.DBClearer;
import org.unitils.dbmaintainer.script.ExecutedScript;
import org.unitils.dbmaintainer.script.Script;
import org.unitils.dbmaintainer.script.ScriptRunner;
import org.unitils.dbmaintainer.script.ScriptSource;
import org.unitils.dbmaintainer.structure.ConstraintsDisabler;
import org.unitils.dbmaintainer.structure.DataSetStructureGenerator;
import org.unitils.dbmaintainer.structure.SequenceUpdater;
import static org.unitils.dbmaintainer.util.DatabaseModuleConfigUtils.getConfiguredDatabaseTaskInstance;
import org.unitils.dbmaintainer.version.ExecutedScriptInfoSource;
import org.unitils.dbmaintainer.version.Version;
import org.unitils.util.PropertyUtils;

import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * A class for performing automatic maintenance of a database.
* This class must be configured with implementations of a {@link ExecutedScriptInfoSource}, * {@link ScriptSource}, a {@link ScriptRunner}, {@link DBClearer}, {@link DBCleaner}, * {@link ConstraintsDisabler}, {@link SequenceUpdater} and a {@link DataSetStructureGenerator} *

The {@link #updateDatabase()} method check what is the current version of the database, and * see if existing scripts have been modified. If yes, the database is cleared and all available * database scripts, are executed on the database. If no existing scripts have been modified, but * new scripts were added, only the new scripts are executed. Before executing an update, data from * the database is removed, to avoid problems when e.g. adding a not null column.

If a database * update causes an error, a {@link UnitilsException} is thrown. After a failing update, the * database is always completely recreated from scratch.

After updating the database, following * steps are optionally executed on the database (depending on the configuration): *

    *
  • Foreign key and not null constraints are disabled.
  • *
  • Sequences and identity columns that have a value lower than a configured treshold, are * updated to a value equal to or larger than this treshold
  • *
  • A DTD is generated that describes the database's table structure, to use in test data XML * files
  • *
*

To obtain a properly configured DBMaintainer, invoke the constructor * {@link #DBMaintainer(Properties,SQLHandler)} with a TestDataSource providing * access to the database and a Configuration object containing all necessary * properties. * * @author Filip Neven * @author Tim Ducheyne */ public class DBMaintainer { /* The logger instance for this class */ private static Log logger = LogFactory.getLog(DBMaintainer.class); /** * Property indicating if deleting all data from all tables before updating is enabled */ public static final String PROPKEY_DB_CLEANER_ENABLED = "dbMaintainer.cleanDb.enabled"; /** * Property indicating if updating the database from scratch is enabled */ public static final String PROPKEY_FROM_SCRATCH_ENABLED = "dbMaintainer.fromScratch.enabled"; /** * Property indicating if database code should be cleared before installing a new version of * the code or when updating the database from scratch */ public static final String PROPKEY_CLEAR_DB_CODE_ENABLED = "dbMaintainer.clearDbCode.enabled"; /** * Property indicating if an retry of an update should only be performed when changes to script files were made */ public static final String PROPKEY_KEEP_RETRYING_AFTER_ERROR_ENABLED = "dbMaintainer.keepRetryingAfterError.enabled"; /** * Property indicating if the database constraints should org disabled after updating the database */ public static final String PROPKEY_DISABLE_CONSTRAINTS_ENABLED = "dbMaintainer.disableConstraints.enabled"; /** * Property indicating if the database constraints should org disabled after updating the database */ public static final String PROPKEY_UPDATE_SEQUENCES_ENABLED = "dbMaintainer.updateSequences.enabled"; /** * Property that indicates if a data set DTD or XSD is to be generated or not */ public static final String PROPKEY_GENERATE_DATA_SET_STRUCTURE_ENABLED = "dbMaintainer.generateDataSetStructure.enabled"; /** * Provider of the current version of the database, and means to increment it */ protected ExecutedScriptInfoSource versionSource; /** * Provider of scripts for updating the database to a higher version */ protected ScriptSource scriptSource; /** * Executer of the scripts */ protected ScriptRunner scriptRunner; /** * Clearer of the database (removed all tables, sequences, ...) before updating */ protected DBClearer dbClearer; /** * Cleaner of the database (deletes all data from all tables before updating */ protected DBCleaner dbCleaner; /** * Disabler of constraints */ protected ConstraintsDisabler constraintsDisabler; /** * Database sequence updater */ protected SequenceUpdater sequenceUpdater; /** * Database DTD generator */ protected DataSetStructureGenerator dataSetStructureGenerator; /** * Indicates whether updating the database from scratch is enabled. If true, the database is * cleared before updating if an already executed script is modified */ protected boolean fromScratchEnabled; /** * Indicates if foreign key and not null constraints should removed after updating the database * structure */ protected boolean disableConstraintsEnabled; /** * Indicates whether a from scratch update should be performed when the previous update failed, * but none of the scripts were modified since that last update. If true a new update will be * tried only when changes were made to the script files */ protected boolean keepRetryingAfterError; /** * Default constructor for testing. */ protected DBMaintainer() { } /** * Create a new instance of DBMaintainer, The concrete implementations of all * helper classes are derived from the given Configuration object. * * @param configuration the configuration, not null * @param sqlHandler the data source, not null */ public DBMaintainer(Properties configuration, SQLHandler sqlHandler) { try { scriptRunner = getConfiguredDatabaseTaskInstance(ScriptRunner.class, configuration, sqlHandler); versionSource = getConfiguredDatabaseTaskInstance(ExecutedScriptInfoSource.class, configuration, sqlHandler); scriptSource = ConfigUtils.getConfiguredInstanceOf(ScriptSource.class, configuration); boolean cleanDbEnabled = PropertyUtils.getBoolean(PROPKEY_DB_CLEANER_ENABLED, configuration); if (cleanDbEnabled) { dbCleaner = getConfiguredDatabaseTaskInstance(DBCleaner.class, configuration, sqlHandler); } fromScratchEnabled = PropertyUtils.getBoolean(PROPKEY_FROM_SCRATCH_ENABLED, configuration); keepRetryingAfterError = PropertyUtils.getBoolean(PROPKEY_KEEP_RETRYING_AFTER_ERROR_ENABLED, configuration); if (fromScratchEnabled) { dbClearer = getConfiguredDatabaseTaskInstance(DBClearer.class, configuration, sqlHandler); } disableConstraintsEnabled = PropertyUtils.getBoolean(PROPKEY_DISABLE_CONSTRAINTS_ENABLED, configuration); constraintsDisabler = getConfiguredDatabaseTaskInstance(ConstraintsDisabler.class, configuration, sqlHandler); boolean updateSequences = PropertyUtils.getBoolean(PROPKEY_UPDATE_SEQUENCES_ENABLED, configuration); if (updateSequences) { sequenceUpdater = getConfiguredDatabaseTaskInstance(SequenceUpdater.class, configuration, sqlHandler); } boolean generateDtd = PropertyUtils.getBoolean(PROPKEY_GENERATE_DATA_SET_STRUCTURE_ENABLED, configuration); if (generateDtd) { dataSetStructureGenerator = getConfiguredDatabaseTaskInstance(DataSetStructureGenerator.class, configuration, sqlHandler); } } catch (UnitilsException e) { logger.error("Error while initializing DbMaintainer", e); throw e; } } /** * Checks if the new scripts are available to update the version of the database. If yes, these * scripts are executed and the version number is increased. If an existing script has been * modified, the database is cleared and completely rebuilt from scratch. If an error occurs * with one of the scripts, a {@link UnitilsException} is thrown. */ public void updateDatabase() { // Check if the executed scripts info source recommends a from-scratch update boolean fromScratchUpdateRecommended = versionSource.isFromScratchUpdateRecommended(); Set alreadyExecutedScripts = versionSource.getExecutedScripts(); Version highestExecutedScriptVersion = getHighestExecutedScriptVersion(alreadyExecutedScripts); // check whether an from scratch update should be performed boolean shouldUpdateFromScratch = shouldUpdateDatabaseFromScratch(highestExecutedScriptVersion, alreadyExecutedScripts); if (fromScratchEnabled && (fromScratchUpdateRecommended || shouldUpdateFromScratch)) { // From scratch needed, clear the database and retrieve scripts // constraints are removed before clearing the database, to be sure there will be no // conflicts when dropping tables constraintsDisabler.disableConstraints(); dbClearer.clearSchemas(); // reset the database version versionSource.clearAllExecutedScripts(); // update database with all scripts updateDatabase(scriptSource.getAllUpdateScripts()); return; } // perform an incremental update updateDatabase(scriptSource.getNewScripts(highestExecutedScriptVersion, alreadyExecutedScripts)); } protected Version getHighestExecutedScriptVersion(Set executedScripts) { Version highest = new Version("0"); for (ExecutedScript executedScript : executedScripts) { if (executedScript.getScript().isIncremental()) { if (executedScript.getScript().getVersion().compareTo(highest) > 0) { highest = executedScript.getScript().getVersion(); } } } return highest; } /** * Updates the database version to the current version of the update scripts, without changing * anything else in the database. Can be used to initialize the database for future updates, * knowning that the current state of the database is synchronized with the current state of the * scripts. */ public void resetDatabaseState() { versionSource.clearAllExecutedScripts(); List