package liquibase.serializer.core.xml;
import liquibase.change.Change;
import liquibase.change.ChangeProperty;
import liquibase.change.ColumnConfig;
import liquibase.change.ConstraintsConfig;
import liquibase.change.TextNode;
import liquibase.change.custom.CustomChangeWrapper;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.parser.core.xml.LiquibaseEntityResolver;
import liquibase.parser.core.xml.XMLChangeLogSAXParser;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.sql.visitor.SqlVisitor;
import liquibase.util.ISODateFormat;
import liquibase.util.StringUtils;
import liquibase.util.XMLUtil;
import liquibase.util.xml.DefaultXmlWriter;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
public class XMLChangeLogSerializer implements ChangeLogSerializer {
private Document currentChangeLogFileDOM;
public XMLChangeLogSerializer() {
}
protected XMLChangeLogSerializer(Document currentChangeLogFileDOM) {
this.currentChangeLogFileDOM = currentChangeLogFileDOM;
}
public void setCurrentChangeLogFileDOM(Document currentChangeLogFileDOM) {
this.currentChangeLogFileDOM = currentChangeLogFileDOM;
}
public String[] getValidFileExtensions() {
return new String[] {"xml"};
}
public String serialize(DatabaseChangeLog databaseChangeLog) {
return null; //todo
}
public String serialize(Change change) {
StringBuffer buffer = new StringBuffer();
nodeToStringBuffer(createNode(change), buffer);
return buffer.toString();
}
public String serialize(SqlVisitor visitor) {
StringBuffer buffer = new StringBuffer();
nodeToStringBuffer(createNode(visitor), buffer);
return buffer.toString();
}
public String serialize(ColumnConfig columnConfig) {
StringBuffer buffer = new StringBuffer();
nodeToStringBuffer(createNode(columnConfig), buffer);
return buffer.toString();
}
public String serialize(ChangeSet changeSet) {
StringBuffer buffer = new StringBuffer();
nodeToStringBuffer(createNode(changeSet), buffer);
return buffer.toString();
}
public void write(List<ChangeSet> changeSets, OutputStream out)
throws IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = factory.newDocumentBuilder();
}
catch(ParserConfigurationException e) {
throw new RuntimeException(e);
}
documentBuilder.setEntityResolver(new LiquibaseEntityResolver());
Document doc = documentBuilder.newDocument();
Element changeLogElement = doc.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "databaseChangeLog");
changeLogElement.setAttribute("xmlns", XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace());
changeLogElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
changeLogElement.setAttribute("xsi:schemaLocation", "http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-"+ XMLChangeLogSAXParser.getSchemaVersion()+ ".xsd");
doc.appendChild(changeLogElement);
setCurrentChangeLogFileDOM(doc);
for (ChangeSet changeSet : changeSets) {
doc.getDocumentElement().appendChild(createNode(changeSet));
}
new DefaultXmlWriter().write(doc, out);
}
public Element createNode(SqlVisitor visitor) {
Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), visitor.getName());
try {
List<Field> allFields = new ArrayList<Field>();
Class classToExtractFieldsFrom = visitor.getClass();
while (!classToExtractFieldsFrom.equals(Object.class)) {
allFields.addAll(Arrays.asList(classToExtractFieldsFrom.getDeclaredFields()));
classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperclass();
}
for (Field field : allFields) {
field.setAccessible(true);
ChangeProperty changePropertyAnnotation = field.getAnnotation(ChangeProperty.class);
if (changePropertyAnnotation != null && !changePropertyAnnotation.includeInSerialization()) {
continue;
}
if (field.getName().equals("serialVersionUID")) {
continue;
}
if (field.getName().equals("$VRc")) { //from emma
continue;
}
String propertyName = field.getName();
Object value = field.get(visitor);
if (value != null) {
node.setAttribute(propertyName, value.toString());
}
}
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
return node;
}
public Element createNode(Change change) {
Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), change.getChangeMetaData().getName());
try {
List<Field> allFields = new ArrayList<Field>();
Class classToExtractFieldsFrom = change.getClass();
while (!classToExtractFieldsFrom.equals(Object.class)) {
allFields.addAll(Arrays.asList(classToExtractFieldsFrom.getDeclaredFields()));
classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperclass();
}
for (Field field : allFields) {
field.setAccessible(true);
ChangeProperty changePropertyAnnotation = field.getAnnotation(ChangeProperty.class);
if (changePropertyAnnotation != null && !changePropertyAnnotation.includeInSerialization()) {
continue;
}
if (field.getName().equals("serialVersionUID")) {
continue;
}
if (field.getName().equals("$VRc")) { //from emma
continue;
}
// String properties annotated with @TextNode are serialized as a child node
TextNode textNodeAnnotation = field.getAnnotation(TextNode.class);
if (textNodeAnnotation != null) {
String textNodeContent = (String) field.get(change);
node.appendChild(createNode(textNodeAnnotation.nodeName(), textNodeContent));
continue;
}
String propertyName = field.getName();
if (field.getType().equals(ColumnConfig.class)) {
node.appendChild(createNode((ColumnConfig) field.get(change)));
} else if (Collection.class.isAssignableFrom(field.getType())) {
for (Object object : (Collection) field.get(change)) {
if (object instanceof ColumnConfig) {
node.appendChild(createNode((ColumnConfig) object));
}
}
} else {
Object value = field.get(change);
if (value != null) {
if (propertyName.equals("procedureBody")
|| propertyName.equals("sql")
|| propertyName.equals("selectQuery")) {
node.setTextContent(value.toString());
} else {
node.setAttribute(propertyName, value.toString());
}
}
}
}
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
return node;
}
// create a XML node with nodeName and simple text content
public Element createNode(String nodeName, String nodeContent) {
Element element = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), nodeName);
element.setTextContent(nodeContent);
return element;
}
public Element createNode(ColumnConfig columnConfig) {
Element element = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "column");
if (columnConfig.getName() != null) {
element.setAttribute("name", columnConfig.getName());
}
if (columnConfig.getType() != null) {
element.setAttribute("type", columnConfig.getType());
}
if (columnConfig.getDefaultValue() != null) {
element.setAttribute("defaultValue", columnConfig.getDefaultValue());
}
if (columnConfig.getDefaultValueNumeric() != null) {
element.setAttribute("defaultValueNumeric", columnConfig.getDefaultValueNumeric().toString());
}
if (columnConfig.getDefaultValueDate() != null) {
element.setAttribute("defaultValueDate", new ISODateFormat().format(columnConfig.getDefaultValueDate()));
}
if (columnConfig.getDefaultValueBoolean() != null) {
element.setAttribute("defaultValueBoolean", columnConfig.getDefaultValueBoolean().toString());
}
if (columnConfig.getDefaultValueComputed() != null) {
element.setAttribute("defaultValueComputed", columnConfig.getDefaultValueComputed().toString());
}
if (columnConfig.getValue() != null) {
element.setAttribute("value", columnConfig.getValue());
}
if (columnConfig.getValueNumeric() != null) {
element.setAttribute("valueNumeric", columnConfig.getValueNumeric().toString());
}
if (columnConfig.getValueBoolean() != null) {
element.setAttribute("valueBoolean", columnConfig.getValueBoolean().toString());
}
if (columnConfig.getValueDate() != null) {
element.setAttribute("valueDate", new ISODateFormat().format(columnConfig.getValueDate()));
}
if (columnConfig.getValueComputed() != null) {
element.setAttribute("valueComputed", columnConfig.getValueComputed().toString());
}
if (StringUtils.trimToNull(columnConfig.getRemarks()) != null) {
element.setAttribute("remarks", columnConfig.getRemarks());
}
if (columnConfig.isAutoIncrement() != null && columnConfig.isAutoIncrement()) {
element.setAttribute("autoIncrement", "true");
}
ConstraintsConfig constraints = columnConfig.getConstraints();
if (constraints != null) {
Element constraintsElement = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "constraints");
if (constraints.getCheck() != null) {
constraintsElement.setAttribute("check", constraints.getCheck());
}
if (constraints.getForeignKeyName() != null) {
constraintsElement.setAttribute("foreignKeyName", constraints.getForeignKeyName());
}
if (constraints.getReferences() != null) {
constraintsElement.setAttribute("references", constraints.getReferences());
}
if (constraints.isDeferrable() != null) {
constraintsElement.setAttribute("deferrable", constraints.isDeferrable().toString());
}
if (constraints.isDeleteCascade() != null) {
constraintsElement.setAttribute("deleteCascade", constraints.isDeleteCascade().toString());
}
if (constraints.isInitiallyDeferred() != null) {
constraintsElement.setAttribute("initiallyDeferred", constraints.isInitiallyDeferred().toString());
}
if (constraints.isNullable() != null) {
constraintsElement.setAttribute("nullable", constraints.isNullable().toString());
}
if (constraints.isPrimaryKey() != null) {
constraintsElement.setAttribute("primaryKey", constraints.isPrimaryKey().toString());
}
if (constraints.isUnique() != null) {
constraintsElement.setAttribute("unique", constraints.isUnique().toString());
}
if (constraints.getUniqueConstraintName() != null) {
constraintsElement.setAttribute("uniqueConstraintName", constraints.getUniqueConstraintName());
}
if (constraints.getPrimaryKeyName() != null) {
constraintsElement.setAttribute("primaryKeyName", constraints.getPrimaryKeyName());
}
if (constraints.getPrimaryKeyTablespace() != null) {
constraintsElement.setAttribute("primaryKeyTablespace", constraints.getPrimaryKeyTablespace());
}
element.appendChild(constraintsElement);
}
return element;
}
public Element createNode(ChangeSet changeSet) {
Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "changeSet");
node.setAttribute("id", changeSet.getId());
node.setAttribute("author", changeSet.getAuthor());
if (changeSet.isAlwaysRun()) {
node.setAttribute("runAlways", "true");
}
if (changeSet.isRunOnChange()) {
node.setAttribute("runOnChange", "true");
}
if (changeSet.getFailOnError() != null) {
node.setAttribute("failOnError", changeSet.getFailOnError().toString());
}
if (changeSet.getContexts() != null && changeSet.getContexts().size() > 0) {
StringBuffer contextString = new StringBuffer();
for (String context : changeSet.getContexts()) {
contextString.append(context).append(",");
}
node.setAttribute("context", contextString.toString().replaceFirst(",$", ""));
}
if (changeSet.getDbmsSet() != null && changeSet.getDbmsSet().size() > 0) {
StringBuffer dbmsString = new StringBuffer();
for (String dbms : changeSet.getDbmsSet()) {
dbmsString.append(dbms).append(",");
}
node.setAttribute("dbms", dbmsString.toString().replaceFirst(",$", ""));
}
if (StringUtils.trimToNull(changeSet.getComments()) != null) {
Element commentsElement = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "comment");
Text commentsText = currentChangeLogFileDOM.createTextNode(changeSet.getComments());
commentsElement.appendChild(commentsText);
node.appendChild(commentsElement);
}
for (Change change : changeSet.getChanges()) {
node.appendChild(createNode(change));
}
if (changeSet.getRollBackChanges()!=null && changeSet.getRollBackChanges().length > 0) {
Element rollback = currentChangeLogFileDOM.createElement("rollback");
for (Change change : changeSet.getRollBackChanges()) {
rollback.appendChild(createNode(change));
}
node.appendChild( rollback );
}
return node;
}
public Element createNode(CustomChangeWrapper change) {
Element customElement = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "custom");
customElement.setAttribute("class", change.getClassName());
for (String param : change.getParams()) {
Element paramElement = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "param");
paramElement.setAttribute("name", param);
paramElement.setAttribute("value", change.getParamValues().get(param));
customElement.appendChild(paramElement);
}
return customElement;
}
/*
* Creates a {@link String} using the XML element representation of this
* change
*
* @param node the {@link Element} associated to this change
* @param buffer a {@link StringBuffer} object used to hold the {@link String}
* representation of the change
*/
private void nodeToStringBuffer(Node node, StringBuffer buffer) {
buffer.append("<").append(node.getNodeName());
SortedMap<String, String> attributeMap = new TreeMap<String, String>();
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attribute = attributes.item(i);
attributeMap.put(attribute.getNodeName(), attribute.getNodeValue());
}
for (Map.Entry entry : attributeMap.entrySet()) {
String value = (String) entry.getValue();
if (value != null) {
buffer.append(" ").append(entry.getKey()).append("=\"").append(value).append("\"");
}
}
buffer.append(">").append(StringUtils.trimToEmpty(XMLUtil.getTextContent(node)));
NodeList childNodes = node.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
nodeToStringBuffer(((Element) childNode), buffer);
}
}
buffer.append("</").append(node.getNodeName()).append(">");
}
}