liquibase.hub.listener.HubChangeExecListener Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liquibase-core Show documentation
Show all versions of liquibase-core Show documentation
Liquibase is a tool for managing and executing database changes.
package liquibase.hub.listener;
import liquibase.Scope;
import liquibase.change.Change;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.visitor.AbstractChangeExecListener;
import liquibase.changelog.visitor.ChangeExecListener;
import liquibase.changelog.visitor.ChangeLogSyncListener;
import liquibase.hub.HubConfiguration;
import liquibase.database.Database;
import liquibase.exception.LiquibaseException;
import liquibase.exception.PreconditionErrorException;
import liquibase.exception.PreconditionFailedException;
import liquibase.hub.HubService;
import liquibase.hub.HubServiceFactory;
import liquibase.hub.LiquibaseHubException;
import liquibase.hub.model.HubChangeLog;
import liquibase.hub.model.Operation;
import liquibase.hub.model.OperationChangeEvent;
import liquibase.logging.Logger;
import liquibase.logging.core.BufferedLogService;
import liquibase.precondition.core.PreconditionContainer;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.serializer.ChangeLogSerializerFactory;
import liquibase.sql.Sql;
import liquibase.sqlgenerator.SqlGeneratorFactory;
import liquibase.statement.SqlStatement;
import liquibase.util.StringUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.logging.Level;
public class HubChangeExecListener extends AbstractChangeExecListener
implements ChangeExecListener, ChangeLogSyncListener {
private static final Logger logger = Scope.getCurrentScope().getLog(HubChangeExecListener.class);
private final Operation operation;
private final Map startDateMap = new HashMap<>();
private String rollbackScriptContents;
private int postCount;
private int failedToPostCount;
private final ChangeExecListener changeExecListener;
public HubChangeExecListener(Operation operation, ChangeExecListener changeExecListener) {
this.operation = operation;
this.changeExecListener = changeExecListener;
}
public void setRollbackScriptContents(String rollbackScriptContents) {
this.rollbackScriptContents = rollbackScriptContents;
}
public int getPostCount() {
return postCount;
}
public int getFailedToPostCount() {
return failedToPostCount;
}
@Override
public void willRun(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database, ChangeSet.RunStatus runStatus) {
startDateMap.put(changeSet, new Date());
if (changeExecListener != null) {
changeExecListener.willRun(changeSet, databaseChangeLog, database, runStatus);
}
}
@Override
public void willRun(Change change, ChangeSet changeSet, DatabaseChangeLog changeLog, Database database) {
startDateMap.put(changeSet, new Date());
if (changeExecListener != null) {
changeExecListener.willRun(change, changeSet, changeLog, database);
}
}
@Override
public void ran(ChangeSet changeSet,
DatabaseChangeLog databaseChangeLog,
Database database,
ChangeSet.ExecType execType) {
String message = "PASSED::" + changeSet.getId() + "::" + changeSet.getAuthor();
updateHub(changeSet, databaseChangeLog, database, "UPDATE", "PASS", message);
if (changeExecListener != null) {
changeExecListener.ran(changeSet, databaseChangeLog, database, execType);
}
}
/**
* Called before a change is rolled back.
*
* @param changeSet changeSet that was rolled back
* @param databaseChangeLog parent change log
* @param database the database the rollback was executed on.
*/
@Override
public void willRollback(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database) {
startDateMap.put(changeSet, new Date());
if (changeExecListener != null) {
changeExecListener.willRollback(changeSet, databaseChangeLog, database);
}
}
/**
*
* Called when there is a rollback failure
*
* @param changeSet changeSet that was rolled back
* @param databaseChangeLog parent change log
* @param database the database the rollback was executed on.
* @param e original exception
*
*/
@Override
public void rollbackFailed(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database, Exception e) {
updateHubForRollback(changeSet, databaseChangeLog, database, "FAIL", e.getMessage());
if (changeExecListener != null) {
changeExecListener.rollbackFailed(changeSet, databaseChangeLog, database, e);
}
}
/**
*
* Called which a changeset is successfully rolled back
*
* @param changeSet changeSet that was rolled back
* @param databaseChangeLog parent change log
* @param database the database the rollback was executed on.
*
*/
@Override
public void rolledBack(ChangeSet changeSet,
DatabaseChangeLog databaseChangeLog,
Database database) {
String message = "PASSED::" + changeSet.getId() + "::" + changeSet.getAuthor();
updateHubForRollback(changeSet, databaseChangeLog, database, "PASS", message);
if (changeExecListener != null) {
changeExecListener.rolledBack(changeSet, databaseChangeLog, database);
}
}
@Override
public void preconditionFailed(PreconditionFailedException error, PreconditionContainer.FailOption onFail) {
if (changeExecListener != null) {
changeExecListener.preconditionFailed(error, onFail);
}
}
@Override
public void preconditionErrored(PreconditionErrorException error, PreconditionContainer.ErrorOption onError) {
if (changeExecListener != null) {
changeExecListener.preconditionErrored(error, onError);
}
}
@Override
public void ran(Change change, ChangeSet changeSet, DatabaseChangeLog changeLog, Database database) {
if (changeExecListener != null) {
changeExecListener.ran(change, changeSet, changeLog, database);
}
}
@Override
public void runFailed(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database, Exception exception) {
updateHub(changeSet, databaseChangeLog, database, "UPDATE", "FAIL", exception.getMessage());
if (changeExecListener != null) {
changeExecListener.runFailed(changeSet, databaseChangeLog, database, exception);
}
}
@Override
public void markedRan(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database) {
startDateMap.put(changeSet, new Date());
String message = "PASSED::" + changeSet.getId() + "::" + changeSet.getAuthor();
updateHub(changeSet, databaseChangeLog, database, "SYNC", "PASS", message);
}
//
// Send an update message to Hub for this changeset rollback
//
private void updateHubForRollback(ChangeSet changeSet,
DatabaseChangeLog databaseChangeLog,
Database database,
String operationStatusType,
String statusMessage) {
if (operation == null) {
String apiKey = StringUtil.trimToNull(HubConfiguration.LIQUIBASE_HUB_API_KEY.getCurrentValue());
boolean hubOn = HubConfiguration.LIQUIBASE_HUB_MODE.getCurrentValue() != HubConfiguration.HubMode.OFF;
if (apiKey != null && hubOn) {
String message =
"Hub communication failure.\n" +
"The data for operation on changeset '" +
changeSet.getId() +
"' by author '" + changeSet.getAuthor() + "'\n" +
"was not successfully recorded in your Liquibase Hub project";
Scope.getCurrentScope().getUI().sendMessage(message);
logger.info(message);
}
return;
}
HubChangeLog hubChangeLog;
final HubService hubService = Scope.getCurrentScope().getSingleton(HubServiceFactory.class).getService();
try {
hubChangeLog = hubService.getHubChangeLog(UUID.fromString(databaseChangeLog.getChangeLogId()));
if (hubChangeLog == null) {
logger.warning("The changelog '" + databaseChangeLog.getPhysicalFilePath() + "' has not been registered with Hub");
return;
}
}
catch (LiquibaseHubException lhe) {
logger.warning("The changelog '" + databaseChangeLog.getPhysicalFilePath() + "' has not been registered with Hub");
return;
}
Date dateExecuted = new Date();
//
// POST /organizations/{id}/projects/{id}/operations/{id}/change-events
//
OperationChangeEvent operationChangeEvent = new OperationChangeEvent();
operationChangeEvent.setEventType("ROLLBACK");
operationChangeEvent.setStartDate(startDateMap.get(changeSet));
operationChangeEvent.setEndDate(dateExecuted);
operationChangeEvent.setDateExecuted(dateExecuted);
operationChangeEvent.setChangesetId(changeSet.getId());
operationChangeEvent.setChangesetFilename(changeSet.getFilePath());
operationChangeEvent.setChangesetAuthor(changeSet.getAuthor());
List sqlList = new ArrayList<>();
try {
if (rollbackScriptContents != null) {
sqlList.add(rollbackScriptContents);
}
else if (changeSet.hasCustomRollbackChanges()) {
List changes = changeSet.getRollback().getChanges();
for (Change change : changes) {
SqlStatement[] statements = change.generateStatements(database);
for (SqlStatement statement : statements) {
for (Sql sql : SqlGeneratorFactory.getInstance().generateSql(statement, database)) {
sqlList.add(sql.toSql());
}
}
}
}
else {
List changes = changeSet.getChanges();
for (Change change : changes) {
SqlStatement[] statements = change.generateRollbackStatements(database);
for (SqlStatement statement : statements) {
for (Sql sql : SqlGeneratorFactory.getInstance().generateSql(statement, database)) {
sqlList.add(sql.toSql());
}
}
}
}
}
catch (LiquibaseException lbe) {
logger.warning(lbe.getMessage());
}
String[] sqlArray = new String[sqlList.size()];
sqlArray = sqlList.toArray(sqlArray);
operationChangeEvent.setGeneratedSql(sqlArray);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ChangeLogSerializer serializer =
ChangeLogSerializerFactory.getInstance().getSerializer(".json");
try {
serializer.write(Collections.singletonList(changeSet), baos);
operationChangeEvent.setChangesetBody(baos.toString(StandardCharsets.UTF_8.name()));
}
catch (IOException ioe) {
//
// Consume
//
}
operationChangeEvent.setOperationStatusType(operationStatusType);
operationChangeEvent.setStatusMessage(statusMessage);
if ("FAIL".equals(operationStatusType)) {
operationChangeEvent.setLogs(statusMessage);
}
else {
String logs = getCurrentLog();
if (! StringUtil.isEmpty(logs)) {
operationChangeEvent.setLogs(logs);
}
else {
operationChangeEvent.setLogs(statusMessage);
}
}
operationChangeEvent.setLogsTimestamp(new Date());
operationChangeEvent.setProject(hubChangeLog.getProject());
operationChangeEvent.setOperation(operation);
try {
hubService.sendOperationChangeEvent(operationChangeEvent);
postCount++;
}
catch (LiquibaseException lbe) {
logger.warning(lbe.getMessage(), lbe);
logger.warning("Unable to send Operation Change Event for operation '" + operation.getId().toString() +
" changeset '" + changeSet.toString(false));
}
}
private String getCurrentLog() {
//
// Capture the current log level to use for filtering
//
Level currentLevel = HubConfiguration.LIQUIBASE_HUB_LOGLEVEL.getCurrentValue();
BufferedLogService bufferedLogService =
Scope.getCurrentScope().get(BufferedLogService.class.getName(), BufferedLogService.class);
if (bufferedLogService != null) {
return bufferedLogService.getLogAsString(currentLevel);
}
return null;
}
//
// Send an update message to Hub for this changeset
//
private void updateHub(ChangeSet changeSet,
DatabaseChangeLog databaseChangeLog,
Database database,
String eventType,
String operationStatusType,
String statusMessage) {
//
// If not connected to Hub but we are supposed to be then show message
//
if (operation == null) {
return;
}
HubChangeLog hubChangeLog;
final HubService hubService = Scope.getCurrentScope().getSingleton(HubServiceFactory.class).getService();
try {
hubChangeLog = hubService.getHubChangeLog(UUID.fromString(databaseChangeLog.getChangeLogId()));
if (hubChangeLog == null) {
logger.warning("The changelog '" + databaseChangeLog.getPhysicalFilePath() + "' has not been registered with Hub");
return;
}
}
catch (LiquibaseHubException lhe) {
logger.warning("The changelog '" + databaseChangeLog.getPhysicalFilePath() + "' has not been registered with Hub");
return;
}
//
// POST /organizations/{id}/projects/{id}/operations/{id}/change-events
// Do not send generated SQL or changeset body for changeLogSync operation
//
OperationChangeEvent operationChangeEvent = new OperationChangeEvent();
List sqlList = new ArrayList<>();
if (! eventType.equals("SYNC")) {
List changes = changeSet.getChanges();
for (Change change : changes) {
try {
Sql[] sqls = SqlGeneratorFactory.getInstance().generateSql(change, database);
for (Sql sql : sqls) {
sqlList.add(sql.toSql());
}
}
catch (Exception e) {
logger.warning("Unable to generate SQL for Hub failure message: " + e.getMessage());
}
}
String[] sqlArray = new String[sqlList.size()];
sqlArray = sqlList.toArray(sqlArray);
operationChangeEvent.setGeneratedSql(sqlArray);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ChangeLogSerializer serializer = ChangeLogSerializerFactory.getInstance().getSerializer(".json");
try {
serializer.write(Collections.singletonList(changeSet), baos);
operationChangeEvent.setChangesetBody(baos.toString(StandardCharsets.UTF_8.name()));
} catch (IOException ioe) {
//
// Just log message
//
logger.warning("Unable to serialize changeset '" + changeSet.toString(false) + "' for Hub.");
}
}
Date dateExecuted = new Date();
String[] sqlArray = new String[sqlList.size()];
sqlArray = sqlList.toArray(sqlArray);
operationChangeEvent.setEventType(eventType);
operationChangeEvent.setStartDate(startDateMap.get(changeSet));
operationChangeEvent.setEndDate(dateExecuted);
operationChangeEvent.setDateExecuted(dateExecuted);
operationChangeEvent.setChangesetId(changeSet.getId());
operationChangeEvent.setChangesetFilename(changeSet.getFilePath());
operationChangeEvent.setChangesetAuthor(changeSet.getAuthor());
operationChangeEvent.setOperationStatusType(operationStatusType);
operationChangeEvent.setStatusMessage(statusMessage);
operationChangeEvent.setGeneratedSql(sqlArray);
operationChangeEvent.setOperation(operation);
operationChangeEvent.setLogsTimestamp(new Date());
if ("FAIL".equals(operationStatusType)) {
operationChangeEvent.setLogs(statusMessage);
}
else {
String logs = getCurrentLog();
if (! StringUtil.isEmpty(logs)) {
operationChangeEvent.setLogs(logs);
}
else {
operationChangeEvent.setLogs(statusMessage);
}
}
operationChangeEvent.setProject(hubChangeLog.getProject());
operationChangeEvent.setOperation(operation);
try {
hubService.sendOperationChangeEvent(operationChangeEvent);
postCount++;
}
catch (LiquibaseException lbe) {
logger.warning(lbe.getMessage(), lbe);
logger.warning("Unable to send Operation Change Event for operation '" + operation.getId().toString() +
" changeset '" + changeSet.toString(false));
failedToPostCount++;
}
}
public ChangeExecListener getChangeExecListener() {
return changeExecListener;
}
}