liquibase.diff.output.changelog.DiffToChangeLog Maven / Gradle / Ivy
package liquibase.diff.output.changelog;
import liquibase.change.Change;
import liquibase.changelog.ChangeSet;
import liquibase.configuration.GlobalConfiguration;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.database.Database;
import liquibase.database.ObjectQuotingStrategy;
import liquibase.database.OfflineConnection;
import liquibase.database.core.*;
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.UnexpectedLiquibaseException;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogService;
import liquibase.logging.LogType;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.serializer.ChangeLogSerializerFactory;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.DatabaseObjectComparator;
import liquibase.structure.core.Column;
import liquibase.util.DependencyUtil;
import liquibase.util.StringUtils;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class DiffToChangeLog {
public static final String ORDER_ATTRIBUTE = "order";
public static final String DATABASE_CHANGE_LOG_CLOSING_XML_TAG = "";
private String idRoot = String.valueOf(new Date().getTime());
private boolean overriddenIdRoot;
private int changeNumber = 1;
private String changeSetContext;
private String changeSetAuthor;
private String changeSetPath;
private DiffResult diffResult;
private DiffOutputControl diffOutputControl;
private boolean tryDbaDependencies=true;
private static Set loggedOrderFor = new HashSet<>();
public DiffToChangeLog(DiffResult diffResult, DiffOutputControl diffOutputControl) {
this.diffResult = diffResult;
this.diffOutputControl = diffOutputControl;
}
public DiffToChangeLog(DiffOutputControl diffOutputControl) {
this.diffOutputControl = diffOutputControl;
}
public void setDiffResult(DiffResult diffResult) {
this.diffResult = diffResult;
}
public void setChangeSetContext(String changeSetContext) {
this.changeSetContext = changeSetContext;
}
public void print(String changeLogFile) throws ParserConfigurationException, IOException, DatabaseException {
this.changeSetPath = changeLogFile;
ChangeLogSerializer changeLogSerializer = ChangeLogSerializerFactory.getInstance().getSerializer(changeLogFile);
this.print(changeLogFile, changeLogSerializer);
}
public void print(PrintStream out) throws ParserConfigurationException, IOException, DatabaseException {
this.print(out, ChangeLogSerializerFactory.getInstance().getSerializer("xml"));
}
public void print(String changeLogFile, ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException, IOException, DatabaseException {
this.changeSetPath = changeLogFile;
File file = new File(changeLogFile);
if (!file.exists()) {
LogService.getLog(getClass()).info(LogType.LOG, file + " does not exist, creating");
FileOutputStream stream = new FileOutputStream(file);
print(new PrintStream(stream, true, LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class).getOutputEncoding()), changeLogSerializer);
stream.close();
} else {
LogService.getLog(getClass()).info(LogType.LOG, file + " exists, appending");
ByteArrayOutputStream out = new ByteArrayOutputStream();
print(new PrintStream(out, true, LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class).getOutputEncoding()), changeLogSerializer);
String xml = new String(out.toByteArray(), LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class).getOutputEncoding());
String innerXml = xml.replaceFirst("(?ms).*]*>", "");
innerXml = innerXml.replaceFirst(DATABASE_CHANGE_LOG_CLOSING_XML_TAG, "");
innerXml = innerXml.trim();
if ("".equals(innerXml)) {
LogService.getLog(getClass()).info(LogType.LOG, "No changes found, nothing to do");
return;
}
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw")) {
String line;
long offset = 0;
boolean foundEndTag = false;
while ((line = randomAccessFile.readLine()) != null) {
int index = line.indexOf(DATABASE_CHANGE_LOG_CLOSING_XML_TAG);
if (index >= 0) {
foundEndTag = true;
break;
} else {
offset = randomAccessFile.getFilePointer();
}
}
String lineSeparator = LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration
.class).getOutputLineSeparator();
if (foundEndTag) {
randomAccessFile.seek(offset);
randomAccessFile.writeBytes(" ");
randomAccessFile.write(innerXml.getBytes(LiquibaseConfiguration.getInstance().getConfiguration
(GlobalConfiguration.class).getOutputEncoding()));
randomAccessFile.writeBytes(lineSeparator);
randomAccessFile.writeBytes(DATABASE_CHANGE_LOG_CLOSING_XML_TAG + lineSeparator);
} else {
randomAccessFile.seek(0);
randomAccessFile.write(xml.getBytes(LiquibaseConfiguration.getInstance().getConfiguration
(GlobalConfiguration.class).getOutputEncoding()));
}
randomAccessFile.close();
}
}
}
/**
* Prints changeLog that would bring the target database to be the same as
* the reference database
*/
public void print(PrintStream out, ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException, IOException, DatabaseException {
List changeSets = generateChangeSets();
changeLogSerializer.write(changeSets, out);
out.flush();
}
public List generateChangeSets() {
final ChangeGeneratorFactory changeGeneratorFactory = ChangeGeneratorFactory.getInstance();
DatabaseObjectComparator comparator = new DatabaseObjectComparator();
String created = null;
if (LiquibaseConfiguration.getInstance().getProperty(GlobalConfiguration.class, GlobalConfiguration.GENERATE_CHANGESET_CREATED_VALUES).getValue(Boolean.class)) {
created = new SimpleDateFormat("yyyy-MM-dd HH:mmZ").format(new Date());
}
List> types = getOrderedOutputTypes(ChangedObjectChangeGenerator.class);
List updateChangeSets = new ArrayList();
for (Class extends DatabaseObject> type : types) {
ObjectQuotingStrategy quotingStrategy = diffOutputControl.getObjectQuotingStrategy();
for (Map.Entry extends DatabaseObject, ObjectDifferences> entry : diffResult.getChangedObjects(type, comparator).entrySet()) {
if (!diffResult.getReferenceSnapshot().getDatabase().isLiquibaseObject(entry.getKey()) && !diffResult.getReferenceSnapshot().getDatabase().isSystemObject(entry.getKey())) {
Change[] changes = changeGeneratorFactory.fixChanged(entry.getKey(), entry.getValue(), diffOutputControl, diffResult.getReferenceSnapshot().getDatabase(), diffResult.getComparisonSnapshot().getDatabase());
addToChangeSets(changes, updateChangeSets, quotingStrategy, created);
}
}
}
types = getOrderedOutputTypes(MissingObjectChangeGenerator.class);
List missingObjects = new ArrayList();
for (Class extends DatabaseObject> type : types) {
for (DatabaseObject object : diffResult.getMissingObjects(type, new DatabaseObjectComparator() {
@Override
public int compare(DatabaseObject o1, DatabaseObject o2) {
if ((o1 instanceof Column) && (o1.getAttribute(ORDER_ATTRIBUTE, Integer.class) != null) &&
(o2.getAttribute(ORDER_ATTRIBUTE, Integer.class) != null)) {
int i = o1.getAttribute(ORDER_ATTRIBUTE, Integer.class).compareTo(o2.getAttribute(ORDER_ATTRIBUTE, Integer.class));
if (i != 0) {
return i;
}
}
return super.compare(o1, o2);
}
})) {
if (object == null) {
continue;
}
if (!diffResult.getReferenceSnapshot().getDatabase().isLiquibaseObject(object) && !diffResult.getReferenceSnapshot().getDatabase().isSystemObject(object)) {
missingObjects.add(object);
}
}
}
List createChangeSets = new ArrayList();
for (DatabaseObject object : sortMissingObjects(missingObjects, diffResult.getReferenceSnapshot().getDatabase())) {
ObjectQuotingStrategy quotingStrategy = diffOutputControl.getObjectQuotingStrategy();
Change[] changes = changeGeneratorFactory.fixMissing(object, diffOutputControl, diffResult.getReferenceSnapshot().getDatabase(), diffResult.getComparisonSnapshot().getDatabase());
addToChangeSets(changes, createChangeSets, quotingStrategy, created);
}
List deleteChangeSets = new ArrayList();
types = getOrderedOutputTypes(UnexpectedObjectChangeGenerator.class);
for (Class extends DatabaseObject> type : types) {
ObjectQuotingStrategy quotingStrategy = diffOutputControl.getObjectQuotingStrategy();
for (DatabaseObject object : sortUnexpectedObjects(diffResult.getUnexpectedObjects(type, comparator), diffResult.getReferenceSnapshot().getDatabase())) {
if (!diffResult.getComparisonSnapshot().getDatabase().isLiquibaseObject(object) && !diffResult.getComparisonSnapshot().getDatabase().isSystemObject(object)) {
Change[] changes = changeGeneratorFactory.fixUnexpected(object, diffOutputControl, diffResult.getReferenceSnapshot().getDatabase(), diffResult.getComparisonSnapshot().getDatabase());
addToChangeSets(changes, deleteChangeSets, quotingStrategy, created);
}
}
}
List changeSets = new ArrayList();
changeSets.addAll(createChangeSets);
changeSets.addAll(deleteChangeSets);
changeSets.addAll(updateChangeSets);
return changeSets;
}
private List sortUnexpectedObjects(Collection extends DatabaseObject> unexpectedObjects, Database database) {
return sortObjects("unexpected", (Collection) unexpectedObjects, database);
}
private List sortMissingObjects(Collection missingObjects, Database database) {
return sortObjects("missing", (Collection) missingObjects, database);
}
private List sortObjects(final String type, Collection objects, Database database) {
if ((diffOutputControl.getSchemaComparisons() != null) && !objects.isEmpty() && supportsSortingObjects
(database) && (database.getConnection() != null) && !(database.getConnection() instanceof OfflineConnection)) {
List schemas = new ArrayList<>();
CompareControl.SchemaComparison[] schemaComparisons = this.diffOutputControl.getSchemaComparisons();
if (schemaComparisons != null) {
for (CompareControl.SchemaComparison comparison : schemaComparisons) {
String schemaName = comparison.getReferenceSchema().getSchemaName();
if (schemaName == null) {
schemaName = database.getDefaultSchemaName();
}
schemas.add(schemaName);
}
}
if (schemas.isEmpty()) {
schemas.add(database.getDefaultSchemaName());
}
try {
final List dependencyOrder = new ArrayList<>();
DependencyUtil.NodeValueListener nameListener = new DependencyUtil.NodeValueListener() {
@Override
public void evaluating(String nodeValue) {
dependencyOrder.add(nodeValue);
}
};
DependencyUtil.DependencyGraph graph = new DependencyUtil.DependencyGraph(nameListener);
addDependencies(graph, schemas, database);
graph.computeDependencies();
if (!dependencyOrder.isEmpty()) {
final List toSort = new ArrayList<>();
final List toNotSort = new ArrayList<>();
for (DatabaseObject obj : objects) {
if (!(obj instanceof Column)) {
String schemaName = null;
if (obj.getSchema() != null) {
schemaName = obj.getSchema().getName();
}
String name = schemaName + "." + obj.getName();
if (dependencyOrder.contains(name)) {
toSort.add(obj);
} else {
toNotSort.add(obj);
}
} else {
toNotSort.add(obj);
}
}
Collections.sort(toSort, new Comparator() {
@Override
public int compare(DatabaseObject o1, DatabaseObject o2) {
String o1Schema = null;
if (o1.getSchema() != null) {
o1Schema = o1.getSchema().getName();
}
String o2Schema = null;
if (o2.getSchema() != null) {
o2Schema = o2.getSchema().getName();
}
Integer o1Order = dependencyOrder.indexOf(o1Schema + "." + o1.getName());
int o2Order = dependencyOrder.indexOf(o2Schema + "." + o2.getName());
int order = o1Order.compareTo(o2Order);
if ("unexpected".equals(type)) {
order = order * -1;
}
return order;
}
});
toSort.addAll(toNotSort);
return toSort;
}
} catch (DatabaseException e) {
LogService.getLog(getClass()).debug(LogType.LOG, "Cannot get object dependencies: " + e.getMessage());
}
}
return new ArrayList<>(objects);
}
private List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy