Java tutorial
/** * Copyright 2014 www.migratebird.com * * 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 com.migratebird.script.executedscriptinfo.impl; import static com.migratebird.utils.DbUtils.closeQuietly; 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.util.Date; import java.util.Iterator; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.migratebird.database.Database; import com.migratebird.database.SQLHandler; import com.migratebird.script.ExecutedScript; import com.migratebird.script.Script; import com.migratebird.script.ScriptFactory; import com.migratebird.script.executedscriptinfo.ExecutedScriptInfoSource; import com.migratebird.util.MigrateBirdException; /** * Implementation of <code>VersionSource</code> that stores the version in the database. * */ public class DefaultExecutedScriptInfoSource implements ExecutedScriptInfoSource { /* The logger instance for this class */ private static Log logger = LogFactory.getLog(DefaultExecutedScriptInfoSource.class); protected SortedSet<ExecutedScript> cachedExecutedScripts; protected Database defaultDatabase; protected SQLHandler sqlHandler; /* 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 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; /* True if the scripts table was checked and was valid */ protected boolean validExecutedScriptsTable = false; protected ScriptFactory scriptFactory; public DefaultExecutedScriptInfoSource(boolean autoCreateExecutedScriptsTable, String executedScriptsTableName, String fileNameColumnName, int fileNameColumnSize, String fileLastModifiedAtColumnName, String checksumColumnName, int checksumColumnSize, String executedAtColumnName, int executedAtColumnSize, String succeededColumnName, DateFormat timestampFormat, Database defaultSupport, SQLHandler sqlHandler, ScriptFactory scriptFactory) { this.defaultDatabase = defaultSupport; this.sqlHandler = sqlHandler; this.autoCreateExecutedScriptsTable = autoCreateExecutedScriptsTable; this.executedScriptsTableName = defaultDatabase.toCorrectCaseIdentifier(executedScriptsTableName); this.fileNameColumnName = defaultDatabase.toCorrectCaseIdentifier(fileNameColumnName); this.fileNameColumnSize = fileNameColumnSize; this.fileLastModifiedAtColumnName = defaultDatabase.toCorrectCaseIdentifier(fileLastModifiedAtColumnName); this.checksumColumnName = defaultDatabase.toCorrectCaseIdentifier(checksumColumnName); this.checksumColumnSize = checksumColumnSize; this.executedAtColumnName = defaultDatabase.toCorrectCaseIdentifier(executedAtColumnName); this.executedAtColumnSize = executedAtColumnSize; this.succeededColumnName = defaultDatabase.toCorrectCaseIdentifier(succeededColumnName); this.timestampFormat = timestampFormat; this.scriptFactory = scriptFactory; } /** * @return All scripts that were registered as executed on the database */ public SortedSet<ExecutedScript> getExecutedScripts() { if (cachedExecutedScripts != null) { return cachedExecutedScripts; } checkExecutedScriptsTable(); cachedExecutedScripts = doGetExecutedScripts(); return cachedExecutedScripts; } /** * Precondition: The table db_executed_scripts must exist * * @return All scripts that were registered as executed on the database */ protected synchronized SortedSet<ExecutedScript> doGetExecutedScripts() { TreeSet<ExecutedScript> executedScripts = new TreeSet<ExecutedScript>(); Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { connection = defaultDatabase.getDataSource().getConnection(); statement = connection.createStatement(); resultSet = statement.executeQuery("select " + fileNameColumnName + ", " + fileLastModifiedAtColumnName + ", " + checksumColumnName + ", " + executedAtColumnName + ", " + succeededColumnName + " from " + getQualifiedExecutedScriptsTableName()); while (resultSet.next()) { String fileName = resultSet.getString(fileNameColumnName); String checkSum = resultSet.getString(checksumColumnName); Long fileLastModifiedAt = resultSet.getLong(fileLastModifiedAtColumnName); Date executedAt = null; try { String executedAtStr = resultSet.getString(executedAtColumnName); if (executedAtStr != null) executedAt = timestampFormat.parse(executedAtStr); } catch (ParseException e) { throw new MigrateBirdException( "Error when parsing date " + executedAt + " using format " + timestampFormat, e); } boolean succeeded = resultSet.getInt(succeededColumnName) == 1; Script script = scriptFactory.createScriptWithoutContent(fileName, fileLastModifiedAt, checkSum); if (!script.isIgnored()) { ExecutedScript executedScript = new ExecutedScript(script, executedAt, succeeded); executedScripts.add(executedScript); } } } catch (SQLException e) { throw new MigrateBirdException("Error while retrieving database version", e); } finally { closeQuietly(connection, statement, resultSet); } 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) { checkExecutedScriptsTable(); if (getExecutedScripts().contains(executedScript)) { updateExecutedScript(executedScript); } else { insertExecutedScript(executedScript); } } /** * Saves the given registered script * Precondition: The table db_executed_scripts must exist * * @param executedScript The script that needs to be saved, not null */ protected void insertExecutedScript(ExecutedScript executedScript) { getExecutedScripts().add(executedScript); String executedAt = timestampFormat.format(executedScript.getExecutedAt()); String insertSql = "insert into " + getQualifiedExecutedScriptsTableName() + " (" + fileNameColumnName + ", " + fileLastModifiedAtColumnName + ", " + checksumColumnName + ", " + executedAtColumnName + ", " + succeededColumnName + ") values ('" + executedScript.getScript().getFileName() + "', " + executedScript.getScript().getFileLastModifiedAt() + ", '" + executedScript.getScript().getCheckSum() + "', '" + executedAt + "', " + (executedScript.isSuccessful() ? "1" : "0") + ")"; sqlHandler.executeUpdateAndCommit(insertSql, defaultDatabase.getDataSource()); } /** * Updates the given registered script * * @param executedScript The script that needs to be updated, not null */ public void updateExecutedScript(ExecutedScript executedScript) { checkExecutedScriptsTable(); getExecutedScripts().add(executedScript); String executedAt = timestampFormat.format(executedScript.getExecutedAt()); String updateSql = "update " + getQualifiedExecutedScriptsTableName() + " set " + checksumColumnName + " = '" + executedScript.getScript().getCheckSum() + "', " + fileLastModifiedAtColumnName + " = " + executedScript.getScript().getFileLastModifiedAt() + ", " + executedAtColumnName + " = '" + executedAt + "', " + succeededColumnName + " = " + (executedScript.isSuccessful() ? "1" : "0") + " where " + fileNameColumnName + " = '" + executedScript.getScript().getFileName() + "'"; sqlHandler.executeUpdateAndCommit(updateSql, defaultDatabase.getDataSource()); } /** * Remove the given executed script from the executed scripts * * @param executedScript The executed script, which is no longer part of the executed scripts */ public void deleteExecutedScript(ExecutedScript executedScript) { checkExecutedScriptsTable(); getExecutedScripts().remove(executedScript); String deleteSql = "delete from " + getQualifiedExecutedScriptsTableName() + " where " + fileNameColumnName + " = '" + executedScript.getScript().getFileName() + "'"; sqlHandler.executeUpdateAndCommit(deleteSql, defaultDatabase.getDataSource()); } /** * Registers the fact that the script that was originally executed has been renamed. * * @param executedScript the original executed script that still refers to the original script * @param renamedToScript the script to which the original script has been renamed */ public void renameExecutedScript(ExecutedScript executedScript, Script renamedToScript) { checkExecutedScriptsTable(); String renameSql = "update " + getQualifiedExecutedScriptsTableName() + " set " + fileNameColumnName + " = '" + renamedToScript.getFileName() + "', " + checksumColumnName + " = '" + renamedToScript.getCheckSum() + "', " + fileLastModifiedAtColumnName + " = " + renamedToScript.getFileLastModifiedAt() + " where " + fileNameColumnName + " = '" + executedScript.getScript().getFileName() + "'"; sqlHandler.executeUpdateAndCommit(renameSql, defaultDatabase.getDataSource()); executedScript.renameTo(renamedToScript); } public void deleteAllExecutedPostprocessingScripts() { checkExecutedScriptsTable(); for (Iterator<ExecutedScript> executedScriptsIterator = getExecutedScripts() .iterator(); executedScriptsIterator.hasNext();) { ExecutedScript executedScript = executedScriptsIterator.next(); if (executedScript.getScript().isPostProcessingScript()) { executedScriptsIterator.remove(); String deleteSql = "delete from " + getQualifiedExecutedScriptsTableName() + " where " + fileNameColumnName + " = '" + executedScript.getScript().getFileName() + "'"; sqlHandler.executeUpdateAndCommit(deleteSql, defaultDatabase.getDataSource()); } } } /** * Clears all script executions that have been registered. After having invoked this method, * {@link #getExecutedScripts()} will return an empty set. */ public void clearAllExecutedScripts() { checkExecutedScriptsTable(); String deleteSql = "delete from " + getQualifiedExecutedScriptsTableName(); sqlHandler.executeUpdateAndCommit(deleteSql, defaultDatabase.getDataSource()); resetCachedState(); } /** * Marks the failed scripts in the executed scripts table as successful. */ public void markErrorScriptsAsSuccessful() { checkExecutedScriptsTable(); String deleteSql = "update " + getQualifiedExecutedScriptsTableName() + " set " + succeededColumnName + "=1 where " + succeededColumnName + "=0"; sqlHandler.executeUpdateAndCommit(deleteSql, defaultDatabase.getDataSource()); resetCachedState(); } /** * Removes the failed scripts in the executed scripts table. */ public void removeErrorScripts() { checkExecutedScriptsTable(); String deleteSql = "delete from " + getQualifiedExecutedScriptsTableName() + " where " + succeededColumnName + "=0"; sqlHandler.executeUpdateAndCommit(deleteSql, defaultDatabase.getDataSource()); resetCachedState(); } /** * 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() { if (validExecutedScriptsTable) { return true; } // check valid if (isExecutedScriptsTableValid()) { validExecutedScriptsTable = true; return true; } // does not exist yet, if auto-create create version table if (autoCreateExecutedScriptsTable) { logger.warn("Executed scripts table " + getQualifiedExecutedScriptsTableName() + " 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 " + getQualifiedExecutedScriptsTableName() + " doesn't exist yet or is invalid.\n"; message += "Please create it manually or let MigrateBird create it automatically by setting the property autoCreateMigrateBirdScriptsTable to true.\n"; message += "The table can be created manually by executing following statement:\n"; message += getCreateExecutedScriptTableStatement(); throw new MigrateBirdException(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<String> tableNames = defaultDatabase.getTableNames(defaultDatabase.getDefaultSchemaName()); if (tableNames.contains(executedScriptsTableName)) { // Check columns of version table Set<String> columnNames = defaultDatabase.getColumnNames(defaultDatabase.getDefaultSchemaName(), executedScriptsTableName); if (columnNames.contains(fileNameColumnName) && 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 { defaultDatabase.dropTable(defaultDatabase.getDefaultSchemaName(), executedScriptsTableName); } catch (MigrateBirdException e) { // ignored } // Create db version table sqlHandler.executeUpdateAndCommit(getCreateExecutedScriptTableStatement(), defaultDatabase.getDataSource()); } /** * @return The statement to create the version table. */ protected String getCreateExecutedScriptTableStatement() { String longDataType = defaultDatabase.getLongDataType(); return "create table " + getQualifiedExecutedScriptsTableName() + " ( " + fileNameColumnName + " " + defaultDatabase.getTextDataType(fileNameColumnSize) + ", " + fileLastModifiedAtColumnName + " " + defaultDatabase.getLongDataType() + ", " + checksumColumnName + " " + defaultDatabase.getTextDataType(checksumColumnSize) + ", " + executedAtColumnName + " " + defaultDatabase.getTextDataType(executedAtColumnSize) + ", " + succeededColumnName + " " + longDataType + " )"; } protected String getQualifiedExecutedScriptsTableName() { return defaultDatabase.qualified(defaultDatabase.getDefaultSchemaName(), executedScriptsTableName); } /** * Resets the cached state, for example when the scripts table was modified by another process. * The scripts will be reloaded the next time. */ public void resetCachedState() { cachedExecutedScripts = null; } }