liquibase.diff.output.changelog.DiffToChangeLog Maven / Gradle / Ivy
package liquibase.diff.output.changelog;
import liquibase.CatalogAndSchema;
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.diff.DiffResult;
import liquibase.diff.ObjectDifferences;
import liquibase.diff.output.DiffOutputControl;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.logging.LogFactory;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.serializer.ChangeLogSerializerFactory;
import liquibase.serializer.LiquibaseSerializable;
import liquibase.serializer.core.xml.XMLChangeLogSerializer;
import liquibase.structure.DatabaseObject;
import liquibase.structure.DatabaseObjectComparator;
import liquibase.structure.core.*;
import liquibase.util.StringUtils;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DiffToChangeLog {
private String idRoot = String.valueOf(new Date().getTime());
private int changeNumber = 1;
private String changeSetContext;
private String changeSetAuthor;
private String changeSetPath;
private DiffResult diffResult;
private DiffOutputControl diffOutputControl;
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 {
ChangeLogSerializer changeLogSerializer = ChangeLogSerializerFactory.getInstance().getSerializer(changeLogFile);
this.print(changeLogFile, changeLogSerializer);
}
public void print(PrintStream out) throws ParserConfigurationException, IOException, DatabaseException {
this.print(out, new XMLChangeLogSerializer());
}
public void print(String changeLogFile, ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException, IOException, DatabaseException {
File file = new File(changeLogFile);
if (!file.exists()) {
LogFactory.getLogger().info(file + " does not exist, creating");
FileOutputStream stream = new FileOutputStream(file);
print(new PrintStream(stream), changeLogSerializer);
stream.close();
} else {
LogFactory.getLogger().info(file + " exists, appending");
ByteArrayOutputStream out = new ByteArrayOutputStream();
print(new PrintStream(out), changeLogSerializer);
String xml = new String(out.toByteArray());
xml = xml.replaceFirst("(?ms).*]*>", "");
xml = xml.replaceFirst(" ", "");
xml = xml.trim();
if ("".equals(xml)) {
LogFactory.getLogger().info("No changes found, nothing to do");
return;
}
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
String line;
long offset = 0;
while ((line = randomAccessFile.readLine()) != null) {
int index = line.indexOf("");
if (index >= 0) {
break;
} else {
offset = randomAccessFile.getFilePointer();
}
}
String lineSeparator = LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class).getOutputLineSeparator();
randomAccessFile.seek(offset);
randomAccessFile.writeBytes(" ");
randomAccessFile.write(xml.getBytes());
randomAccessFile.writeBytes(lineSeparator);
randomAccessFile.writeBytes("" + lineSeparator);
randomAccessFile.close();
// BufferedWriter fileWriter = new BufferedWriter(new
// FileWriter(file));
// fileWriter.append(xml);
// fileWriter.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();
List changeSets = new ArrayList();
List> types = getOrderedOutputTypes(MissingObjectChangeGenerator.class);
for (Class extends DatabaseObject> type : types) {
ObjectQuotingStrategy quotingStrategy = diffOutputControl.getObjectQuotingStrategy();
for (DatabaseObject object : diffResult.getMissingObjects(type, comparator)) {
if (object == null) {
continue;
}
if (!diffResult.getReferenceSnapshot().getDatabase().isLiquibaseObject(object) && !diffResult.getReferenceSnapshot().getDatabase().isSystemObject(object)) {
Change[] changes = changeGeneratorFactory.fixMissing(object, diffOutputControl, diffResult.getReferenceSnapshot().getDatabase(), diffResult.getComparisonSnapshot().getDatabase());
addToChangeSets(changes, changeSets, quotingStrategy);
}
}
}
types = getOrderedOutputTypes(UnexpectedObjectChangeGenerator.class);
for (Class extends DatabaseObject> type : types) {
ObjectQuotingStrategy quotingStrategy = diffOutputControl.getObjectQuotingStrategy();
for (DatabaseObject object : diffResult.getUnexpectedObjects(type, comparator)) {
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, changeSets, quotingStrategy);
}
}
}
types = getOrderedOutputTypes(ChangedObjectChangeGenerator.class);
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, changeSets, quotingStrategy);
}
}
}
return changeSets;
}
protected List> getOrderedOutputTypes(Class extends ChangeGenerator> generatorType) {
Database comparisonDatabase = diffResult.getComparisonSnapshot().getDatabase();
DependencyGraph graph = new DependencyGraph();
for (Class extends DatabaseObject> type : diffResult.getReferenceSnapshot().getSnapshotControl().getTypesToInclude()) {
graph.addType(type);
}
List> types = graph.sort(comparisonDatabase, generatorType);
if (!loggedOrderFor.contains(generatorType)) {
String log = generatorType.getSimpleName()+" type order: ";
for (Class extends DatabaseObject> type : types) {
log += " " + type.getName();
}
LogFactory.getLogger().debug(log);
loggedOrderFor.add(generatorType);
}
return types;
}
private void addToChangeSets(Change[] changes, List changeSets, ObjectQuotingStrategy quotingStrategy) {
if (changes != null) {
ChangeSet changeSet = new ChangeSet(generateId(), getChangeSetAuthor(), false, false, null, changeSetContext,
null, false, quotingStrategy, null);
for (Change change : changes) {
changeSet.addChange(change);
}
changeSets.add(changeSet);
}
}
protected String getChangeSetAuthor() {
if (changeSetAuthor != null) {
return changeSetAuthor;
}
String author = System.getProperty("user.name");
if (StringUtils.trimToNull(author) == null) {
return "diff-generated";
} else {
return author + " (generated)";
}
}
public void setChangeSetAuthor(String changeSetAuthor) {
this.changeSetAuthor = changeSetAuthor;
}
public String getChangeSetPath() {
return changeSetPath;
}
public void setChangeSetPath(String changeSetPath) {
this.changeSetPath = changeSetPath;
}
public void setIdRoot(String idRoot) {
this.idRoot = idRoot;
}
protected String generateId() {
return idRoot + "-" + changeNumber++;
}
private static class DependencyGraph {
private Map, Node> allNodes = new HashMap, Node>();
private void addType(Class extends DatabaseObject> type) {
allNodes.put(type, new Node(type));
}
public List> sort(Database database, Class extends ChangeGenerator> generatorType) {
ChangeGeneratorFactory changeGeneratorFactory = ChangeGeneratorFactory.getInstance();
for (Class extends DatabaseObject> type : allNodes.keySet()) {
for (Class extends DatabaseObject> afterType : changeGeneratorFactory.runBeforeTypes(type, database, generatorType)) {
getNode(type).addEdge(getNode(afterType));
}
for (Class extends DatabaseObject> beforeType : changeGeneratorFactory.runAfterTypes(type, database, generatorType)) {
getNode(beforeType).addEdge(getNode(type));
}
}
ArrayList returnNodes = new ArrayList();
SortedSet nodesWithNoIncomingEdges = new TreeSet(new Comparator() {
@Override
public int compare(Node o1, Node o2) {
return o1.type.getName().compareTo(o2.type.getName());
}
});
for (Node n : allNodes.values()) {
if (n.inEdges.size() == 0) {
nodesWithNoIncomingEdges.add(n);
}
}
while (!nodesWithNoIncomingEdges.isEmpty()) {
Node node = nodesWithNoIncomingEdges.iterator().next();
nodesWithNoIncomingEdges.remove(node);
returnNodes.add(node);
for (Iterator it = node.outEdges.iterator(); it.hasNext(); ) {
//remove edge e from the graph
Edge edge = it.next();
Node nodePointedTo = edge.to;
it.remove();//Remove edge from node
nodePointedTo.inEdges.remove(edge);//Remove edge from nodePointedTo
//if nodePointedTo has no other incoming edges then insert nodePointedTo into nodesWithNoIncomingEdges
if (nodePointedTo.inEdges.isEmpty()) {
nodesWithNoIncomingEdges.add(nodePointedTo);
}
}
}
//Check to see if all edges are removed
for (Node n : allNodes.values()) {
if (!n.inEdges.isEmpty()) {
String message = "Could not resolve " + generatorType.getSimpleName() + " dependencies due to dependency cycle. Dependencies: \n";
for (Node node : allNodes.values()) {
SortedSet fromTypes = new TreeSet();
SortedSet toTypes = new TreeSet();
for (Edge edge : node.inEdges) {
fromTypes.add(edge.from.type.getSimpleName());
}
for (Edge edge : node.outEdges) {
toTypes.add(edge.to.type.getSimpleName());
}
String from = StringUtils.join(fromTypes, ",");
String to = StringUtils.join(toTypes, ",");
message += " ["+ from +"] -> "+ node.type.getSimpleName()+" -> [" + to +"]\n";
}
throw new UnexpectedLiquibaseException(message);
}
}
List> returnList = new ArrayList>();
for (Node node : returnNodes) {
returnList.add(node.type);
}
return returnList;
}
private Node getNode(Class extends DatabaseObject> type) {
Node node = allNodes.get(type);
if (node == null) {
node = new Node(type);
}
return node;
}
static class Node {
public final Class extends DatabaseObject> type;
public final HashSet inEdges;
public final HashSet outEdges;
public Node(Class extends DatabaseObject> type) {
this.type = type;
inEdges = new HashSet();
outEdges = new HashSet();
}
public Node addEdge(Node node) {
Edge e = new Edge(this, node);
outEdges.add(e);
node.inEdges.add(e);
return this;
}
@Override
public String toString() {
return type.getName();
}
}
static class Edge {
public final Node from;
public final Node to;
public Edge(Node from, Node to) {
this.from = from;
this.to = to;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Edge)) {
return false;
}
if (obj == null) {
return false;
}
Edge e = (Edge) obj;
return e.from == from && e.to == to;
}
@Override
public int hashCode() {
return (this.from.toString()+"."+this.to.toString()).hashCode();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy