com.tacitknowledge.util.migration.jdbc.PatchTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of autopatch Show documentation
Show all versions of autopatch Show documentation
An automated Java patching system
The newest version!
/* Copyright 2004 Tacit Knowledge
*
* 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.tacitknowledge.util.migration.jdbc;
import com.tacitknowledge.util.migration.MigrationException;
import com.tacitknowledge.util.migration.PatchInfoStore;
import com.tacitknowledge.util.migration.jdbc.util.SqlUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
/**
* Manages interactions with the "patches" table. The patches table stores
* the current patch level for a given system, as well as a system-scoped lock
* use to avoid concurrent patches to the system. A system is defined as an
* exclusive target of a patch.
*
* This class is responsible for:
*
* - Validating the existence of the patches table and creating it if it
* doesn't exist
* - Determining if a patch is currently running on a given system
* - Obtaining and releasing patch locks for a given system
* - Obtaining and incrementing the patch level for a given system
*
*
* TRANSACTIONS: Transactions should be committed by the calling
* class as needed. This class does not explictly commit or rollback transactions.
*
* @author Scott Askew ([email protected])
*/
public class PatchTable implements PatchInfoStore
{
/**
* Class logger
*/
private static Log log = LogFactory.getLog(PatchTable.class);
/**
* The migration configuration
*/
private JdbcMigrationContext context = null;
/**
* Keeps track of table validation (see #createPatchesTableIfNeeded)
*/
private boolean tableExistenceValidated = false;
/**
* Create a new PatchTable.
*
* @param migrationContext the migration configuration and connection source
*/
public PatchTable(JdbcMigrationContext migrationContext)
{
this.context = migrationContext;
if (context.getDatabaseType() == null)
{
throw new IllegalArgumentException("The JDBC database type is required");
}
}
/**
* {@inheritDoc}
*/
public void createPatchStoreIfNeeded() throws MigrationException
{
if (tableExistenceValidated)
{
return;
}
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.table.exists"));
stmt.setString(1, context.getSystemName());
rs = stmt.executeQuery();
log.debug("'patches' table already exists.");
tableExistenceValidated = true;
}
catch (SQLException e)
{
// logging error in case it's not a simple patch table doesn't exist error
log.debug(e.getMessage());
SqlUtil.close(null, stmt, rs);
// check connection is valid before using, because the getConnection() call
// could have thrown the SQLException
if (null == conn)
{
throw new MigrationException("Unable to create a connection.", e);
}
log.info("'patches' table must not exist; creating....");
try
{
stmt = conn.prepareStatement(getSql("patches.create"));
if (log.isDebugEnabled())
{
log.debug("Creating patches table with SQL '" + getSql("patches.create") + "'");
}
stmt.execute();
context.commit();
// We don't yet have a patch record for this system; create one
createSystemPatchRecord();
}
catch (SQLException sqle)
{
throw new MigrationException("Unable to create patch table", sqle);
}
tableExistenceValidated = true;
log.info("Created 'patches' table.");
}
catch (Exception ex)
{
throw new MigrationException("Unexpected exception while creating patch store.", ex);
}
finally
{
SqlUtil.close(conn, stmt, rs);
}
}
/**
* {@inheritDoc}
*/
public int getPatchLevel() throws MigrationException
{
createPatchStoreIfNeeded();
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.read"));
stmt.setString(1, context.getSystemName());
rs = stmt.executeQuery();
if (rs.next())
{
return rs.getInt(1);
}
SqlUtil.close(conn, stmt, rs);
conn = null;
stmt = null;
rs = null;
return 0;
}
catch (SQLException e)
{
throw new MigrationException("Unable to get patch level", e);
}
finally
{
SqlUtil.close(conn, stmt, rs);
}
}
/**
* {@inheritDoc}
*/
public void updatePatchLevel(int level) throws MigrationException
{
// Make sure a patch record already exists for this system
getPatchLevel();
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.update"));
stmt.setInt(1, level);
stmt.setString(2, context.getSystemName());
stmt.execute();
context.commit();
}
catch (SQLException e)
{
throw new MigrationException("Unable to update patch level", e);
}
finally
{
SqlUtil.close(conn, stmt, null);
}
}
/**
* {@inheritDoc}
*/
public boolean isPatchStoreLocked() throws MigrationException
{
createPatchStoreIfNeeded();
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("lock.read"));
stmt.setString(1, context.getSystemName());
stmt.setString(2, context.getSystemName());
rs = stmt.executeQuery();
if (rs.next())
{
return ("T".equals(rs.getString(1)));
}
else
{
return false;
}
}
catch (SQLException e)
{
throw new MigrationException("Unable to determine if table is locked", e);
}
finally
{
SqlUtil.close(conn, stmt, rs);
}
}
/**
* {@inheritDoc}
*/
public void lockPatchStore() throws MigrationException, IllegalStateException
{
createPatchStoreIfNeeded();
if (!updatePatchLock(true))
{
throw new IllegalStateException("Patch table is already locked!");
}
}
/**
* {@inheritDoc}
*/
public void unlockPatchStore() throws MigrationException
{
updatePatchLock(false);
}
/**
* {@inheritDoc}
*/
public boolean isPatchApplied(int patchLevel) throws MigrationException
{
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.exists"));
stmt.setString(1, context.getSystemName());
stmt.setString(2, String.valueOf(patchLevel));
rs = stmt.executeQuery();
if (rs.next())
{
return patchLevel == rs.getInt(1);
}
else
{
return false;
}
}
catch (SQLException e)
{
throw new MigrationException("Unable to determine if patch has been applied", e);
}
finally
{
SqlUtil.close(conn, stmt, rs);
}
}
public void updatePatchLevelAfterRollBack(int rollbackLevel) throws MigrationException
{
// Make sure a patch record already exists for this system
getPatchLevel();
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.rollback"));
stmt.setInt(1, rollbackLevel);
stmt.setString(2, context.getSystemName());
stmt.execute();
context.commit();
}
catch (SQLException e)
{
throw new MigrationException("Unable to update patch level", e);
}
finally
{
SqlUtil.close(conn, stmt, null);
}
}
/**
* Returns the SQL to execute for the database type associated with this patch table.
*
* @param key the key within database.properties whose
* SQL should be returned
* @return the SQL to execute for the database type associated with this patch table
*/
protected String getSql(String key)
{
return context.getDatabaseType().getProperty(key);
}
/**
* Creates an initial record in the patches table for this system.
*
* @throws SQLException if an unrecoverable database error occurs
* @throws MigrationException if an unrecoverable database error occurs
*/
private void createSystemPatchRecord() throws MigrationException, SQLException
{
String systemName = context.getSystemName();
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql("level.create"));
stmt.setString(1, systemName);
stmt.execute();
context.commit();
log.info("Created patch record for " + systemName);
}
catch (SQLException e)
{
log.error("Error creating patch record for system '" + systemName + "'", e);
throw e;
}
finally
{
SqlUtil.close(conn, stmt, null);
}
}
/**
* Obtains or releases a lock for this system in the patches table. If the lock
* was not obtained or released, then false is returned.
*
* @param lock true if a lock is to be obtained, false
* if it is to be removed
* @return true if the lock was updated successfully, otherwise false
* @throws MigrationException if an unrecoverable database error occurs
*/
private boolean updatePatchLock(boolean lock) throws MigrationException
{
String sqlkey = (lock) ? "lock.obtain" : "lock.release";
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = context.getConnection();
stmt = conn.prepareStatement(getSql(sqlkey));
if (log.isDebugEnabled())
{
log.debug("Updating patch table lock: " + getSql(sqlkey));
}
stmt.setString(1, context.getSystemName());
if (lock)
{
stmt.setString(2, context.getSystemName());
}
int rowsUpdated = stmt.executeUpdate();
boolean lockUpdated = (rowsUpdated == 1);
context.commit();
if (log.isDebugEnabled())
{
log.debug(((lock) ? "Obtained" : "Released") + " lock? " + lockUpdated);
}
return lockUpdated;
}
catch (SQLException e)
{
throw new MigrationException("Unable to update patch lock to " + lock, e);
}
finally
{
SqlUtil.close(conn, stmt, null);
}
}
public Set getPatchesApplied() throws MigrationException
{
createPatchStoreIfNeeded();
Connection connection = null;
PreparedStatement stmt = null;
ResultSet resultSet = null;
Set patches = new HashSet();
try
{
connection = context.getConnection();
stmt = connection.prepareStatement(getSql("patches.all"));
stmt.setString(1, context.getSystemName());
resultSet = stmt.executeQuery();
while (resultSet.next())
{
patches.add(resultSet.getInt("patch_level"));
}
}
catch (SQLException e)
{
throw new MigrationException("Unable to get patch levels", e);
}
finally
{
SqlUtil.close(connection, stmt, resultSet);
}
return patches;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy