/**
* Copyright (C) 2015 Orange
* 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.francetelecom.clara.cloud.db.liquibase;
import liquibase.CatalogAndSchema;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.diff.DiffGeneratorFactory;
import liquibase.diff.DiffResult;
import liquibase.diff.ObjectDifferences;
import liquibase.diff.compare.CompareControl;
import liquibase.diff.output.DiffOutputControl;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.integration.commandline.CommandLineUtils;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.structure.DatabaseObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
/**
* Test helper class wrapping liquibase functions
* Note that using Main class is not correct as it does an exit and hence stops the jvm
*/
public class LiquibaseTestWrapper {
private static Logger logger = LoggerFactory.getLogger(LiquibaseTestWrapper.class.getName());
/**
* Purge a database using dropAll liquibase function
*
* @param dbUrl
* @param dbUser
* @param dbPassword
* @throws SQLException
* @throws LiquibaseException
*/
public void purgeDatabase(String dbUrl, String dbUser, String dbPassword) throws SQLException, LiquibaseException {
logger.debug("Purging database " + dbUrl);
Database db = null;
String nullChangeLogFile = null;
try {
db = createDatabase(dbUrl, dbUser, dbPassword);
Liquibase liquibase = new Liquibase(nullChangeLogFile, null, db);
liquibase.forceReleaseLocks();
liquibase.dropAll();
} finally {
closeDatabase(db);
}
}
/**
* Apply change log using liquibase update function
*
* @param dbUrl
* @param dbUser
* @param dbPassword
* @param changeLogFile
* @throws SQLException
* @throws LiquibaseException
*/
public void applyChangeLog(String dbUrl, String dbUser, String dbPassword, String changeLogFile) throws SQLException, LiquibaseException {
logger.debug("Applying changes in database " + dbUrl + " using " + changeLogFile);
Database db = null;
try {
db = createDatabase(dbUrl, dbUser, dbPassword);
ResourceAccessor resourceAccessor = null;
if (changeLogFile.startsWith("classpath:")) {
resourceAccessor = new ClassLoaderResourceAccessor();
changeLogFile = changeLogFile.substring(10);
} else {
resourceAccessor = new FileSystemResourceAccessor(".");
}
Liquibase liquibase = new Liquibase(changeLogFile, resourceAccessor, db);
liquibase.update("");
} finally {
logger.debug("closing connection to:" + db);
closeDatabase(db);
}
}
/**
* call syncChangeLog function of liquibase
*
* @param dbUrl
* @param dbUser
* @param dbPassword
* @param changeLogFile
* @throws SQLException
* @throws LiquibaseException
*/
public void syncChangeLog(String dbUrl, String dbUser, String dbPassword, String changeLogFile) throws SQLException, LiquibaseException {
logger.debug("Sync changes in database " + dbUrl + " using " + changeLogFile);
Database db = null;
try {
ResourceAccessor resourceAccessor = null;
db = createDatabase(dbUrl, dbUser, dbPassword);
if (changeLogFile.startsWith("classpath:")) {
resourceAccessor = new ClassLoaderResourceAccessor();
changeLogFile = changeLogFile.substring(10);
} else {
resourceAccessor = new FileSystemResourceAccessor(".");
}
Liquibase liquibase = new Liquibase(changeLogFile, resourceAccessor, db);
liquibase.changeLogSync("");
} finally {
closeDatabase(db);
}
}
/**
* Generate liquibase changeLog using liquibase
*
* @param dbUrl
* @param dbUser
* @param dbPassword
* @param changeLogFile
* @return true if changeLog is not empty
* @throws LiquibaseException
* @throws IOException
* @throws SQLException
* @throws ParserConfigurationException
*/
public void generateChangeLog(String dbUrl, String dbUser, String dbPassword, String changeLogFile) throws LiquibaseException, IOException, SQLException, ParserConfigurationException {
logger.info("Generating changelog from database {}@{} to {}", dbUser, dbUrl, changeLogFile);
if (changeLogFile == null) {
changeLogFile = ""; //will output to stdout
}
removeExistingChangeLog(changeLogFile);
Database db = createDatabase(dbUrl, dbUser, dbPassword);
DiffOutputControl requireTablespaceForDiff = getTablespaceOnlyDiff();
CatalogAndSchema[] defaultCatalogAndSchema = new CatalogAndSchema[]{CatalogAndSchema.DEFAULT};
String requireAllTypesForSnapshot = null;
try {
CommandLineUtils.doGenerateChangeLog(changeLogFile, db, defaultCatalogAndSchema, requireAllTypesForSnapshot, "paas", null, null, requireTablespaceForDiff);
} finally {
closeDatabase(db);
}
}
public DiffOutputControl getTablespaceOnlyDiff() {
boolean includeCatalog = false;
boolean includeSchema = false;
boolean includeTablespace = true;
return new DiffOutputControl(includeCatalog, includeSchema, includeTablespace);
}
private void removeExistingChangeLog(String changeLogFile) {
// By default the generateChangeLog command is destructive, and
// Liquibase's attempt to append doesn't work properly. Delete
// fail the build if the file already exists.
File file = new File(changeLogFile);
if (file.exists()) {
logger.warn("ChangeLogFile {} already exists, deleting it!", changeLogFile);
file.delete();
}
}
/**
* Compare 2 databases using diff liquibase function
*
* @param paasTestDbUrl
* @param paasTestDbUser
* @param paasTestDbPassword
* @param paasTestDb2Url
* @param paasTestDb2User
* @param paasTestDb2Password
* @return SimpleDiffResult containing a flag indicating if diffreneces have been found and a text representation of those differences
* @throws SQLException
* @throws LiquibaseException
* @throws IOException
* @throws ParserConfigurationException
*/
public DiffResult diff(String paasTestDbUrl, String paasTestDbUser, String paasTestDbPassword, String paasTestDb2Url, String paasTestDb2User,
String paasTestDb2Password) throws SQLException, LiquibaseException, IOException, ParserConfigurationException {
logger.debug("Running liquibase diff between db: " + paasTestDbUrl + " and db: " + paasTestDb2Url);
Database referenceDatabase = null;
Database targetDatabase = null;
try {
referenceDatabase = createDatabase(paasTestDbUrl, paasTestDbUser, paasTestDbPassword);
targetDatabase = createDatabase(paasTestDb2Url, paasTestDb2User, paasTestDb2Password);
final DiffGeneratorFactory generatorFactory = DiffGeneratorFactory.getInstance();
final CompareControl compareControl = new CompareControl();
final DiffResult diffResult = generatorFactory.compare(referenceDatabase, targetDatabase, compareControl);
boolean ignoreDefaultValueDifference = false;
if (ignoreDefaultValueDifference) {
Map<DatabaseObject, ObjectDifferences> changedObjects = diffResult.getChangedObjects();
for (DatabaseObject changedDbObject : changedObjects.keySet()) {
ObjectDifferences objectDifferences = changedObjects.get(changedDbObject);
if (objectDifferences.removeDifference("defaultValue")) {
logger.info("Ignoring default value for {}", changedDbObject.toString());
}
if (!objectDifferences.hasDifferences()) {
logger.info("removing {}, no difference left.", changedDbObject.toString());
changedObjects.remove(objectDifferences);
}
}
}
return diffResult;
} finally {
closeDatabase(referenceDatabase);
closeDatabase(targetDatabase);
}
}
/**
* Utility method to close a liquibase database
*
* @param db
*/
private void closeDatabase(Database db) {
if (db != null) {
try {
logger.debug("closing connection to:" + db);
db.close();
} catch (DatabaseException e) {
logger.error("unable to close database " + db + " exception:" + e.getMessage());
}
}
}
/**
* Utility method to get a liquibase database object
*
* @param dbUrl
* @param dbUser
* @param dbPassword
* @return
* @throws SQLException
* @throws LiquibaseException
*/
private Database createDatabase(String dbUrl, String dbUser, String dbPassword) throws SQLException, LiquibaseException {
Connection c = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
JdbcConnection liquibaseDbConnection = new JdbcConnection(c);
Liquibase liquibase = new Liquibase(null, null, liquibaseDbConnection);
return liquibase.getDatabase();
}
}