package liquibase.ext.metastore.lockservice; import liquibase.configuration.LiquibaseConfiguration; import liquibase.database.Database; import liquibase.database.ObjectQuotingStrategy; import liquibase.exception.DatabaseException; import liquibase.exception.LockException; import liquibase.executor.Executor; import liquibase.executor.ExecutorService; import liquibase.ext.metastore.configuration.HiveMetastoreConfiguration; import liquibase.ext.metastore.database.HiveMetastoreDatabase; import liquibase.lockservice.DatabaseChangeLogLock; import liquibase.lockservice.StandardLockService; import liquibase.logging.LogFactory; import liquibase.logging.Logger; import liquibase.statement.core.LockDatabaseChangeLogStatement; import liquibase.statement.core.SelectFromDatabaseChangeLogLockStatement; import liquibase.statement.core.UnlockDatabaseChangeLogStatement; import java.sql.SQLException; import java.sql.Statement; public class MetastoreLockService extends StandardLockService { private static final Logger LOG = LogFactory.getInstance().getLog(); private ObjectQuotingStrategy quotingStrategy; private Boolean lockDb = LiquibaseConfiguration.getInstance().getConfiguration(HiveMetastoreConfiguration.class).getLock(); @Override public boolean supports(Database database) { return database instanceof HiveMetastoreDatabase; } @Override public int getPriority() { return PRIORITY_DATABASE; } @Override public boolean hasDatabaseChangeLogLockTable() throws DatabaseException { boolean hasChangeLogLockTable = false; Statement statement = null; try { statement = ((HiveMetastoreDatabase) database).getStatement(); LOG.info("Looking for table '" + database.getDatabaseChangeLogLockTableName() + "'"); statement.executeQuery("SELECT id FROM " + database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName())); hasChangeLogLockTable = true; } catch (SQLException e) { LOG.info("Table '" + database.getDatabaseChangeLogLockTableName() + "' doesn't exists in hive metastore."); hasChangeLogLockTable = false; } catch (ClassNotFoundException e) { LOG.warning("Table '" + database.getDatabaseChangeLogLockTableName() + "' doesn't eixsts in hive metastore.", e); hasChangeLogLockTable = false; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } finally { if (statement != null) { try { statement.close(); } catch (SQLException e) { LOG.warning("Can't close cursor", e); } } } return hasChangeLogLockTable; } @Override public void releaseLock() throws LockException { if (lockDb) { ObjectQuotingStrategy incomingQuotingStrategy = null; if (this.quotingStrategy != null) { incomingQuotingStrategy = database.getObjectQuotingStrategy(); database.setObjectQuotingStrategy(this.quotingStrategy); } Executor executor = ExecutorService.getInstance().getExecutor(database); try { if (this.hasDatabaseChangeLogLockTable()) { executor.comment("Release hive metastore database lock"); database.rollback(); executor.execute(new UnlockDatabaseChangeLogStatement()); database.commit(); } } catch (Exception e) { throw new LockException(e); } finally { try { hasChangeLogLock = false; database.setCanCacheLiquibaseTableInfo(false); LOG.info("Change log lock has been successfully released"); database.rollback(); } catch (DatabaseException e) { LOG.warning("Rollback failed", e); } if (incomingQuotingStrategy != null) { database.setObjectQuotingStrategy(incomingQuotingStrategy); } } } } @Override public boolean acquireLock() throws LockException { if (!lockDb || hasChangeLogLock) { return true; } quotingStrategy = database.getObjectQuotingStrategy(); Executor executor = ExecutorService.getInstance().getExecutor(database); try { database.rollback(); init(); Boolean locked = (Boolean) ExecutorService.getInstance().getExecutor(database).queryForObject(new SelectFromDatabaseChangeLogLockStatement("LOCKED"), Boolean.class); if (locked) { return false; } else { executor.comment("Lock hive metastore database"); executor.execute(new LockDatabaseChangeLogStatement()); database.commit(); LOG.info("Change log lock has been successfully acquired"); hasChangeLogLock = true; database.setCanCacheLiquibaseTableInfo(true); return true; } } catch (Exception e) { throw new LockException(e); } finally { try { database.rollback(); } catch (DatabaseException e) { LOG.warning("Rollback failed", e); } } } @Override public void setDatabase(Database database) { if (lockDb) { super.setDatabase(database); } } @Override public void setChangeLogLockWaitTime(long changeLogLockWaitTime) { if (lockDb) { super.setChangeLogLockWaitTime(changeLogLockWaitTime); } } @Override public void setChangeLogLockRecheckTime(long changeLogLockRecheckTime) { if (lockDb) { super.setChangeLogLockWaitTime(changeLogLockRecheckTime); } } @Override public void init() throws DatabaseException { if (lockDb) { super.init(); } } @Override public boolean hasChangeLogLock() { return !lockDb || super.hasChangeLogLock(); } @Override public void waitForLock() throws LockException { if (lockDb) { super.waitForLock(); } } @Override public DatabaseChangeLogLock[] listLocks() throws LockException { if (!lockDb) { return new DatabaseChangeLogLock[0]; } return super.listLocks(); } @Override public void forceReleaseLock() throws LockException, DatabaseException { if (lockDb) { super.forceReleaseLock(); } } @Override public void reset() { if (lockDb) { super.reset(); } } @Override public void destroy() throws DatabaseException { if (lockDb) { super.destroy(); } } }