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

org.unitils.dbmaintainer.version.impl.DefaultExecutedScriptInfoSource Maven / Gradle / Ivy

There is a newer version: 3.4.6
Show newest version
/*
 * 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.version.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.unitils.core.UnitilsException;
import org.unitils.dbmaintainer.script.ExecutedScript;
import org.unitils.dbmaintainer.script.Script;
import org.unitils.dbmaintainer.util.BaseDatabaseAccessor;
import org.unitils.dbmaintainer.version.ExecutedScriptInfoSource;
import static org.unitils.thirdparty.org.apache.commons.dbutils.DbUtils.closeQuietly;
import org.unitils.util.PropertyUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

/**
 * Implementation of VersionSource that stores the version in the database. The version is stored in the
 * table whose name is defined by the property {@link #PROPERTY_EXECUTED_SCRIPTS_TABLE_NAME}. The version index column name is
 * defined by {@link #PROPERTY_FILE_NAME_COLUMN_NAME}, the version timestamp colmumn name is defined by
 * {@link #PROPERTY_SCRIPT_VERSION_COLUMN_NAME}. The last updated succeeded column name is defined by
 * {@link #PROPERTY_EXECUTED_AT_COLUMN_NAME}.
 *
 * @author Filip Neven
 * @author Tim Ducheyne
 */
public class DefaultExecutedScriptInfoSource extends BaseDatabaseAccessor implements ExecutedScriptInfoSource {

    /* The logger instance for this class */
    private static Log logger = LogFactory.getLog(DefaultExecutedScriptInfoSource.class);

    /* The key of the property that specifies the datase table in which the DB version is stored */
    public static final String PROPERTY_EXECUTED_SCRIPTS_TABLE_NAME = "dbMaintainer.executedScriptsTableName";

    /* The key of the property that specifies the column in which the script filenames are stored */
    public static final String PROPERTY_FILE_NAME_COLUMN_NAME = "dbMaintainer.fileNameColumnName";
    public static final String PROPERTY_FILE_NAME_COLUMN_SIZE = "dbMaintainer.fileNameColumnSize";

    /* The key of the property that specifies the column in which the last modification timestamp is stored */
    public static final String PROPERTY_SCRIPT_VERSION_COLUMN_NAME = "dbMaintainer.versionColumnName";
    public static final String PROPERTY_SCRIPT_VERSION_COLUMN_SIZE = "dbMaintainer.versionColumnSize";

    /* The key of the property that specifies the column in which the last modification timestamp is stored */
    public static final String PROPERTY_FILE_LAST_MODIFIED_AT_COLUMN_NAME = "dbMaintainer.fileLastModifiedAtColumnName";

    /* The key of the property that specifies the column in which the last modification timestamp is stored */
    public static final String PROPERTY_CHECKSUM_COLUMN_NAME = "dbMaintainer.checksumColumnName";
    public static final String PROPERTY_CHECKSUM_COLUMN_SIZE = "dbMaintainer.checksumColumnSize";

    /* The key of the property that specifies the column in which is stored whether the last update succeeded. */
    public static final String PROPERTY_EXECUTED_AT_COLUMN_NAME = "dbMaintainer.executedAtColumnName";
    public static final String PROPERTY_EXECUTED_AT_COLUMN_SIZE = "dbMaintainer.executedAtColumnSize";

    /* The key of the property that specifies the column in which is stored whether the last update succeeded. */
    public static final String PROPERTY_SUCCEEDED_COLUMN_NAME = "dbMaintainer.succeededColumnName";

    /* The key of the property that specifies whether the executec scripts table should be created automatically. */
    public static final String PROPERTY_AUTO_CREATE_EXECUTED_SCRIPTS_TABLE = "dbMaintainer.autoCreateExecutedScriptsTable";

    public static final String PROPERTY_TIMESTAMP_FORMAT = "dbMaintainer.timestampFormat";

    protected Set executedScripts;

    /**
     * The name of the database table in which the executed script info is stored
     */
    protected String executedScriptsTableName;

    /**
     * The name of the database column in which the script name is stored
     */
    protected String fileNameColumnName;
    protected int fileNameColumnSize;

    /**
     * The name of the database column in which the script version is stored
     */
    protected String versionColumnName;
    protected int versionColumnSize;

    /**
     * The name of the database column in which the file last modification timestamp is stored
     */
    protected String fileLastModifiedAtColumnName;

    /**
     * The name of the database column in which the checksum calculated on the script content is stored
     */
    protected String checksumColumnName;
    protected int checksumColumnSize;

    /**
     * The name of the database column in which the script execution timestamp is stored
     */
    protected String executedAtColumnName;
    protected int executedAtColumnSize;

    /**
     * The name of the database column in which the script name is stored
     */
    protected String succeededColumnName;

    /**
     * True if the scripts table should be created automatically if it does not exist yet
     */
    protected boolean autoCreateExecutedScriptsTable;

    /**
     * Format of the contents of the executed_at column
     */
    protected DateFormat timestampFormat;


    /**
     * Initializes the name of the version table and its columns using the given configuration.
     *
     * @param configuration the configuration, not null
     */
    @Override
    protected void doInit(Properties configuration) {
        this.executedScriptsTableName = defaultDbSupport.toCorrectCaseIdentifier(
                PropertyUtils.getString(PROPERTY_EXECUTED_SCRIPTS_TABLE_NAME, configuration));
        this.fileNameColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_FILE_NAME_COLUMN_NAME, configuration));
        this.fileNameColumnSize = PropertyUtils.getInt(PROPERTY_FILE_NAME_COLUMN_SIZE, configuration);
        this.versionColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_SCRIPT_VERSION_COLUMN_NAME, configuration));
        this.versionColumnSize = PropertyUtils.getInt(PROPERTY_SCRIPT_VERSION_COLUMN_SIZE, configuration);
        this.fileLastModifiedAtColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_FILE_LAST_MODIFIED_AT_COLUMN_NAME, configuration));
        this.checksumColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_CHECKSUM_COLUMN_NAME, configuration));
        this.checksumColumnSize = PropertyUtils.getInt(PROPERTY_CHECKSUM_COLUMN_SIZE, configuration);
        this.executedAtColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_EXECUTED_AT_COLUMN_NAME, configuration));
        this.executedAtColumnSize = PropertyUtils.getInt(PROPERTY_EXECUTED_AT_COLUMN_SIZE, configuration);
        this.succeededColumnName = defaultDbSupport.toCorrectCaseIdentifier(PropertyUtils.getString(PROPERTY_SUCCEEDED_COLUMN_NAME, configuration));

        this.autoCreateExecutedScriptsTable = PropertyUtils.getBoolean(PROPERTY_AUTO_CREATE_EXECUTED_SCRIPTS_TABLE, configuration);
        this.timestampFormat = new SimpleDateFormat(PropertyUtils.getString(PROPERTY_TIMESTAMP_FORMAT, configuration));
    }


    /**
     * This method returns whether a from scratch update is recommended: It will return true
     * if the database is in it's initial state (i.e. the dbmaintain_scripts table doesn't exist yet 
     * or is invalid) and the autoCreateExecutedScriptsTable property is set to true.
     * 

* The reasoning behind this is that before executing the first script, it's a good idea to * clear the database in order to start with a clean situation. * * @return True if a from-scratch update is recommended */ public boolean isFromScratchUpdateRecommended() { return !isExecutedScriptsTableValid() && autoCreateExecutedScriptsTable; } /** * @return All scripts that were registered as executed on the database */ public Set getExecutedScripts() { try { return doGetExecutedScripts(); } catch (UnitilsException e) { if (checkExecutedScriptsTable()) { throw e; } // try again, executed scripts table was not ok return doGetExecutedScripts(); } } /** * Precondition: The table dbmaintain_scripts must exist * * @return All scripts that were registered as executed on the database */ protected Set doGetExecutedScripts() { if (executedScripts == null) { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = sqlHandler.getDataSource().getConnection(); st = conn.createStatement(); rs = st.executeQuery("select " + fileNameColumnName + ", " + versionColumnName + ", " + fileLastModifiedAtColumnName + ", " + checksumColumnName + ", " + executedAtColumnName + ", " + succeededColumnName + " from " + defaultDbSupport.qualified(executedScriptsTableName)); executedScripts = new HashSet(); while (rs.next()) { String fileName = rs.getString(fileNameColumnName); String checkSum = rs.getString(checksumColumnName); Long fileLastModifiedAt = rs.getLong(fileLastModifiedAtColumnName); Date executedAt = null; try { executedAt = timestampFormat.parse(rs.getString(executedAtColumnName)); } catch (ParseException e) { throw new UnitilsException("Error when parsing date " + executedAt + " using format " + timestampFormat, e); } Boolean succeeded = rs.getInt(succeededColumnName) == 1 ? Boolean.TRUE : Boolean.FALSE; ExecutedScript executedScript = new ExecutedScript(new Script(fileName, fileLastModifiedAt, checkSum), executedAt, succeeded); executedScripts.add(executedScript); } } catch (SQLException e) { throw new UnitilsException( "Error while retrieving database version", e); } finally { closeQuietly(conn, st, rs); } } return executedScripts; } /** * Registers the fact that the given script has been executed on the database * * @param executedScript The script that was executed on the database */ public void registerExecutedScript(ExecutedScript executedScript) { try { doRegisterExecutedScript(executedScript); } catch (UnitilsException e) { if (checkExecutedScriptsTable()) { throw e; } // try again, version table was not ok doRegisterExecutedScript(executedScript); } } /** * Registers the fact that the given script has been executed on the database * Precondition: The table dbmaintain_scripts must exist * * @param executedScript The script that was executed on the database */ protected void doRegisterExecutedScript(ExecutedScript executedScript) { if (getExecutedScripts().contains(executedScript)) { doUpdateExecutedScript(executedScript); } else { doSaveExecutedScript(executedScript); } } /** * Updates the given registered script * * @param executedScript The script, not null */ public void updateExecutedScript(ExecutedScript executedScript) { try { doUpdateExecutedScript(executedScript); } catch (UnitilsException e) { if (checkExecutedScriptsTable()) { throw e; } // try again, version table was not ok doUpdateExecutedScript(executedScript); } } /** * Saves the given registered script * Precondition: The table dbmaintain_scripts must exist * * @param executedScript The script, not null */ protected void doSaveExecutedScript(ExecutedScript executedScript) { executedScripts.add(executedScript); String executedAt = timestampFormat.format(executedScript.getExecutedAt()); String insertSql = "insert into " + defaultDbSupport.qualified(executedScriptsTableName) + " (" + fileNameColumnName + ", " + versionColumnName + ", " + fileLastModifiedAtColumnName + ", " + checksumColumnName + ", " + executedAtColumnName + ", " + succeededColumnName + ") values ('" + executedScript.getScript().getFileName() + "', '" + executedScript.getScript().getVersion().getIndexesString() + "', " + executedScript.getScript().getFileLastModifiedAt() + ", '" + executedScript.getScript().getCheckSum() + "', '" + executedAt + "', " + (executedScript.isSucceeded() ? "1" : "0") + ")"; sqlHandler.executeUpdateAndCommit(insertSql); } /** * Updates the given registered script * Precondition: The table dbmaintain_scripts must exist * * @param executedScript The script, not null */ protected void doUpdateExecutedScript(ExecutedScript executedScript) { executedScripts.add(executedScript); String executedAt = timestampFormat.format(executedScript.getExecutedAt()); String updateSql = "update " + defaultDbSupport.qualified(executedScriptsTableName) + " set " + checksumColumnName + " = '" + executedScript.getScript().getCheckSum() + "', " + fileLastModifiedAtColumnName + " = " + executedScript.getScript().getFileLastModifiedAt() + ", " + executedAtColumnName + " = '" + executedAt + "', " + succeededColumnName + " = " + (executedScript.isSucceeded() ? "1" : "0") + " where " + fileNameColumnName + " = '" + executedScript.getScript().getFileName() + "'"; sqlHandler.executeUpdateAndCommit(updateSql); } /** * Clears all script executions that have been registered. After having invoked this method, * {@link #getExecutedScripts()} will return an empty set. */ public void clearAllExecutedScripts() { try { doClearAllExecutedScripts(); } catch (UnitilsException e) { if (checkExecutedScriptsTable()) { throw e; } // try again, version table was not ok doClearAllExecutedScripts(); } } protected void doClearAllExecutedScripts() { executedScripts = new HashSet(); String deleteSql = "delete from " + defaultDbSupport.qualified(executedScriptsTableName); sqlHandler.executeUpdateAndCommit(deleteSql); } /** * Checks if the version table and columns are available and if a record exists in which the version info is stored. * If not, the table, columns and record are created if auto-create is true, else an exception is raised. * * @return False if the version table was not ok and therefore auto-created */ protected boolean checkExecutedScriptsTable() { // check valid if (isExecutedScriptsTableValid()) { return true; } // does not exist yet, if auto-create create version table if (autoCreateExecutedScriptsTable) { logger.warn("Executed scripts table " + defaultDbSupport.qualified(executedScriptsTableName) + " doesn't exist yet or is invalid. A new one is created automatically."); createExecutedScriptsTable(); return false; } // throw an exception that shows how to create the version table String message = "Executed scripts table " + defaultDbSupport.qualified(executedScriptsTableName) + " doesn't exist yet or is invalid.\n"; message += "Please create it manually or let Unitils create it automatically by setting the " + PROPERTY_AUTO_CREATE_EXECUTED_SCRIPTS_TABLE + " property to true.\n"; message += "The table can be created manually by executing following statement:\n"; message += getCreateExecutedScriptsTableStatement(); throw new UnitilsException(message); } /** * Checks if the version table and columns are available and if a record exists in which the version info is stored. * If not, the table, columns and record are created. * * @return False if the version table was not ok and therefore re-created */ protected boolean isExecutedScriptsTableValid() { // Check existence of version table Set tableNames = defaultDbSupport.getTableNames(); if (tableNames.contains(executedScriptsTableName)) { // Check columns of version table Set columnNames = defaultDbSupport.getColumnNames(executedScriptsTableName); if (columnNames.contains(fileNameColumnName) && columnNames.contains(versionColumnName) && columnNames.contains(fileLastModifiedAtColumnName) && columnNames.contains(checksumColumnName) && columnNames.contains(executedAtColumnName) && columnNames.contains(succeededColumnName)) { return true; } } return false; } /** * Creates the version table and inserts a version record. */ protected void createExecutedScriptsTable() { // If version table is invalid, drop and re-create try { defaultDbSupport.dropTable(executedScriptsTableName); } catch (UnitilsException e) { // ignored } // Create db version table sqlHandler.executeUpdateAndCommit(getCreateExecutedScriptsTableStatement()); } /** * @return The statement to create the version table. */ protected String getCreateExecutedScriptsTableStatement() { String longDataType = defaultDbSupport.getLongDataType(); return "create table " + defaultDbSupport.qualified(executedScriptsTableName) + " ( " + fileNameColumnName + " " + defaultDbSupport.getTextDataType(fileNameColumnSize) + ", " + versionColumnName + " " + defaultDbSupport.getTextDataType(versionColumnSize) + ", " + fileLastModifiedAtColumnName + " " + defaultDbSupport.getLongDataType() + ", " + checksumColumnName + " " + defaultDbSupport.getTextDataType(checksumColumnSize) + ", " + executedAtColumnName + " " + defaultDbSupport.getTextDataType(executedAtColumnSize) + ", " + succeededColumnName + " " + longDataType + " )"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy