Coverage Report - liquibase.serializer.core.xml.XMLChangeLogSerializer
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLChangeLogSerializer
36%
80/220
45%
64/142
5.529
 
 1  
 package liquibase.serializer.core.xml;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.io.OutputStream;
 5  
 import java.lang.reflect.Field;
 6  
 import java.util.ArrayList;
 7  
 import java.util.Arrays;
 8  
 import java.util.Collection;
 9  
 import java.util.List;
 10  
 import java.util.Map;
 11  
 import java.util.SortedMap;
 12  
 import java.util.TreeMap;
 13  
 
 14  
 import javax.xml.parsers.DocumentBuilder;
 15  
 import javax.xml.parsers.DocumentBuilderFactory;
 16  
 import javax.xml.parsers.ParserConfigurationException;
 17  
 
 18  
 import liquibase.change.Change;
 19  
 import liquibase.change.ChangeProperty;
 20  
 import liquibase.change.ColumnConfig;
 21  
 import liquibase.change.ConstraintsConfig;
 22  
 import liquibase.change.TextNode;
 23  
 import liquibase.change.custom.CustomChangeWrapper;
 24  
 import liquibase.changelog.ChangeSet;
 25  
 import liquibase.changelog.DatabaseChangeLog;
 26  
 import liquibase.exception.UnexpectedLiquibaseException;
 27  
 import liquibase.parser.core.xml.LiquibaseEntityResolver;
 28  
 import liquibase.parser.core.xml.XMLChangeLogSAXParser;
 29  
 import liquibase.serializer.ChangeLogSerializer;
 30  
 import liquibase.sql.visitor.SqlVisitor;
 31  
 import liquibase.util.ISODateFormat;
 32  
 import liquibase.util.StringUtils;
 33  
 import liquibase.util.XMLUtil;
 34  
 import liquibase.util.xml.DefaultXmlWriter;
 35  
 
 36  
 import org.w3c.dom.Document;
 37  
 import org.w3c.dom.Element;
 38  
 import org.w3c.dom.NamedNodeMap;
 39  
 import org.w3c.dom.Node;
 40  
 import org.w3c.dom.NodeList;
 41  
 import org.w3c.dom.Text;
 42  
 
 43  
 public class XMLChangeLogSerializer implements ChangeLogSerializer {
 44  
 
 45  
     private Document currentChangeLogFileDOM;
 46  
 
 47  8
     public XMLChangeLogSerializer() {
 48  8
     }
 49  
 
 50  40
     protected XMLChangeLogSerializer(Document currentChangeLogFileDOM) {
 51  40
         this.currentChangeLogFileDOM = currentChangeLogFileDOM;
 52  40
     }
 53  
 
 54  
     public void setCurrentChangeLogFileDOM(Document currentChangeLogFileDOM) {
 55  0
         this.currentChangeLogFileDOM = currentChangeLogFileDOM;
 56  0
     }
 57  
 
 58  
     @Override
 59  
     public String[] getValidFileExtensions() {
 60  8
         return new String[] { "xml" };
 61  
     }
 62  
 
 63  
     @Override
 64  
     public String serialize(DatabaseChangeLog databaseChangeLog) {
 65  0
         return null; // todo
 66  
     }
 67  
 
 68  
     @Override
 69  
     public String serialize(Change change) {
 70  0
         StringBuffer buffer = new StringBuffer();
 71  0
         nodeToStringBuffer(createNode(change), buffer);
 72  0
         return buffer.toString();
 73  
     }
 74  
 
 75  
     @Override
 76  
     public String serialize(SqlVisitor visitor) {
 77  0
         StringBuffer buffer = new StringBuffer();
 78  0
         nodeToStringBuffer(createNode(visitor), buffer);
 79  0
         return buffer.toString();
 80  
     }
 81  
 
 82  
     @Override
 83  
     public String serialize(ColumnConfig columnConfig) {
 84  0
         StringBuffer buffer = new StringBuffer();
 85  0
         nodeToStringBuffer(createNode(columnConfig), buffer);
 86  0
         return buffer.toString();
 87  
     }
 88  
 
 89  
     @Override
 90  
     public String serialize(ChangeSet changeSet) {
 91  0
         StringBuffer buffer = new StringBuffer();
 92  0
         nodeToStringBuffer(createNode(changeSet), buffer);
 93  0
         return buffer.toString();
 94  
 
 95  
     }
 96  
 
 97  
     @Override
 98  
     public void write(List<ChangeSet> changeSets, OutputStream out) throws IOException {
 99  0
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 100  
         DocumentBuilder documentBuilder;
 101  
         try {
 102  0
             documentBuilder = factory.newDocumentBuilder();
 103  0
         } catch (ParserConfigurationException e) {
 104  0
             throw new RuntimeException(e);
 105  0
         }
 106  0
         documentBuilder.setEntityResolver(new LiquibaseEntityResolver());
 107  
 
 108  0
         Document doc = documentBuilder.newDocument();
 109  0
         Element changeLogElement = doc.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(),
 110  
                 "databaseChangeLog");
 111  
 
 112  0
         changeLogElement.setAttribute("xmlns", XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace());
 113  0
         changeLogElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
 114  0
         changeLogElement.setAttribute("xsi:schemaLocation",
 115  
                 "http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-"
 116  
                         + XMLChangeLogSAXParser.getSchemaVersion() + ".xsd");
 117  
 
 118  0
         doc.appendChild(changeLogElement);
 119  0
         setCurrentChangeLogFileDOM(doc);
 120  
 
 121  0
         for (ChangeSet changeSet : changeSets) {
 122  0
             doc.getDocumentElement().appendChild(createNode(changeSet));
 123  
         }
 124  
 
 125  0
         new DefaultXmlWriter().write(doc, out);
 126  0
     }
 127  
 
 128  
     public Element createNode(SqlVisitor visitor) {
 129  0
         Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(),
 130  
                 visitor.getName());
 131  
         try {
 132  0
             List<Field> allFields = new ArrayList<Field>();
 133  0
             Class classToExtractFieldsFrom = visitor.getClass();
 134  0
             while (!classToExtractFieldsFrom.equals(Object.class)) {
 135  0
                 allFields.addAll(Arrays.asList(classToExtractFieldsFrom.getDeclaredFields()));
 136  0
                 classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperclass();
 137  
             }
 138  
 
 139  0
             for (Field field : allFields) {
 140  0
                 field.setAccessible(true);
 141  0
                 ChangeProperty changePropertyAnnotation = field.getAnnotation(ChangeProperty.class);
 142  0
                 if (changePropertyAnnotation != null && !changePropertyAnnotation.includeInSerialization()) {
 143  0
                     continue;
 144  
                 }
 145  0
                 if (field.getName().equals("serialVersionUID")) {
 146  0
                     continue;
 147  
                 }
 148  0
                 if (field.getName().equals("$VRc")) { // from emma
 149  0
                     continue;
 150  
                 }
 151  
 
 152  0
                 String propertyName = field.getName();
 153  0
                 Object value = field.get(visitor);
 154  0
                 if (value != null) {
 155  0
                     node.setAttribute(propertyName, value.toString());
 156  
                 }
 157  0
             }
 158  0
         } catch (Exception e) {
 159  0
             throw new UnexpectedLiquibaseException(e);
 160  0
         }
 161  
 
 162  0
         return node;
 163  
     }
 164  
 
 165  
     public Element createNode(Change change) {
 166  39
         Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(),
 167  
                 change.getChangeMetaData().getName());
 168  
         try {
 169  39
             List<Field> allFields = new ArrayList<Field>();
 170  39
             Class classToExtractFieldsFrom = change.getClass();
 171  119
             while (!classToExtractFieldsFrom.equals(Object.class)) {
 172  80
                 allFields.addAll(Arrays.asList(classToExtractFieldsFrom.getDeclaredFields()));
 173  80
                 classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperclass();
 174  
             }
 175  
 
 176  39
             for (Field field : allFields) {
 177  294
                 field.setAccessible(true);
 178  294
                 ChangeProperty changePropertyAnnotation = field.getAnnotation(ChangeProperty.class);
 179  294
                 if (changePropertyAnnotation != null && !changePropertyAnnotation.includeInSerialization()) {
 180  119
                     continue;
 181  
                 }
 182  175
                 if (field.getName().equals("serialVersionUID")) {
 183  0
                     continue;
 184  
                 }
 185  175
                 if (field.getName().equals("$VRc")) { // from emma
 186  0
                     continue;
 187  
                 }
 188  
 
 189  
                 // String properties annotated with @TextNode are serialized as a child node
 190  175
                 TextNode textNodeAnnotation = field.getAnnotation(TextNode.class);
 191  175
                 if (textNodeAnnotation != null) {
 192  0
                     String textNodeContent = (String) field.get(change);
 193  0
                     node.appendChild(createNode(textNodeAnnotation.nodeName(), textNodeContent));
 194  0
                     continue;
 195  
                 }
 196  
 
 197  175
                 String propertyName = field.getName();
 198  175
                 if (field.getType().equals(ColumnConfig.class)) {
 199  0
                     node.appendChild(createNode((ColumnConfig) field.get(change)));
 200  175
                 } else if (Collection.class.isAssignableFrom(field.getType())) {
 201  5
                     for (Object object : (Collection) field.get(change)) {
 202  12
                         if (object instanceof ColumnConfig) {
 203  12
                             node.appendChild(createNode((ColumnConfig) object));
 204  
                         }
 205  
                     }
 206  
                 } else {
 207  170
                     Object value = field.get(change);
 208  170
                     if (value != null) {
 209  123
                         if (propertyName.equals("procedureBody") || propertyName.equals("sql")
 210  
                                 || propertyName.equals("selectQuery")) {
 211  3
                             node.setTextContent(value.toString());
 212  
                         } else {
 213  120
                             node.setAttribute(propertyName, value.toString());
 214  
                         }
 215  
                     }
 216  
                 }
 217  175
             }
 218  0
         } catch (Exception e) {
 219  0
             throw new UnexpectedLiquibaseException(e);
 220  39
         }
 221  
 
 222  39
         return node;
 223  
     }
 224  
 
 225  
     // create a XML node with nodeName and simple text content
 226  
     public Element createNode(String nodeName, String nodeContent) {
 227  0
         Element element = currentChangeLogFileDOM.createElementNS(
 228  
                 XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), nodeName);
 229  0
         element.setTextContent(nodeContent);
 230  0
         return element;
 231  
     }
 232  
 
 233  
     public Element createNode(ColumnConfig columnConfig) {
 234  13
         Element element = currentChangeLogFileDOM.createElementNS(
 235  
                 XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "column");
 236  13
         if (columnConfig.getName() != null) {
 237  13
             element.setAttribute("name", columnConfig.getName());
 238  
         }
 239  13
         if (columnConfig.getType() != null) {
 240  6
             element.setAttribute("type", columnConfig.getType());
 241  
         }
 242  
 
 243  13
         if (columnConfig.getDefaultValue() != null) {
 244  2
             element.setAttribute("defaultValue", columnConfig.getDefaultValue());
 245  
         }
 246  13
         if (columnConfig.getDefaultValueNumeric() != null) {
 247  0
             element.setAttribute("defaultValueNumeric", columnConfig.getDefaultValueNumeric().toString());
 248  
         }
 249  13
         if (columnConfig.getDefaultValueDate() != null) {
 250  0
             element.setAttribute("defaultValueDate", new ISODateFormat().format(columnConfig.getDefaultValueDate()));
 251  
         }
 252  13
         if (columnConfig.getDefaultValueBoolean() != null) {
 253  0
             element.setAttribute("defaultValueBoolean", columnConfig.getDefaultValueBoolean().toString());
 254  
         }
 255  13
         if (columnConfig.getDefaultValueComputed() != null) {
 256  0
             element.setAttribute("defaultValueComputed", columnConfig.getDefaultValueComputed().toString());
 257  
         }
 258  
 
 259  13
         if (columnConfig.getValue() != null) {
 260  2
             element.setAttribute("value", columnConfig.getValue());
 261  
         }
 262  13
         if (columnConfig.getValueNumeric() != null) {
 263  3
             element.setAttribute("valueNumeric", columnConfig.getValueNumeric().toString());
 264  
         }
 265  13
         if (columnConfig.getValueBoolean() != null) {
 266  0
             element.setAttribute("valueBoolean", columnConfig.getValueBoolean().toString());
 267  
         }
 268  13
         if (columnConfig.getValueDate() != null) {
 269  0
             element.setAttribute("valueDate", new ISODateFormat().format(columnConfig.getValueDate()));
 270  
         }
 271  13
         if (columnConfig.getValueComputed() != null) {
 272  0
             element.setAttribute("valueComputed", columnConfig.getValueComputed().toString());
 273  
         }
 274  13
         if (StringUtils.trimToNull(columnConfig.getRemarks()) != null) {
 275  0
             element.setAttribute("remarks", columnConfig.getRemarks());
 276  
         }
 277  
 
 278  13
         if (columnConfig.isAutoIncrement() != null && columnConfig.isAutoIncrement()) {
 279  0
             element.setAttribute("autoIncrement", "true");
 280  
         }
 281  
 
 282  13
         ConstraintsConfig constraints = columnConfig.getConstraints();
 283  13
         if (constraints != null) {
 284  4
             Element constraintsElement = currentChangeLogFileDOM.createElementNS(
 285  
                     XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "constraints");
 286  4
             if (constraints.getCheck() != null) {
 287  0
                 constraintsElement.setAttribute("check", constraints.getCheck());
 288  
             }
 289  4
             if (constraints.getForeignKeyName() != null) {
 290  2
                 constraintsElement.setAttribute("foreignKeyName", constraints.getForeignKeyName());
 291  
             }
 292  4
             if (constraints.getReferences() != null) {
 293  2
                 constraintsElement.setAttribute("references", constraints.getReferences());
 294  
             }
 295  4
             if (constraints.isDeferrable() != null) {
 296  2
                 constraintsElement.setAttribute("deferrable", constraints.isDeferrable().toString());
 297  
             }
 298  4
             if (constraints.isDeleteCascade() != null) {
 299  1
                 constraintsElement.setAttribute("deleteCascade", constraints.isDeleteCascade().toString());
 300  
             }
 301  4
             if (constraints.isInitiallyDeferred() != null) {
 302  2
                 constraintsElement.setAttribute("initiallyDeferred", constraints.isInitiallyDeferred().toString());
 303  
             }
 304  4
             if (constraints.isNullable() != null) {
 305  3
                 constraintsElement.setAttribute("nullable", constraints.isNullable().toString());
 306  
             }
 307  4
             if (constraints.isPrimaryKey() != null) {
 308  2
                 constraintsElement.setAttribute("primaryKey", constraints.isPrimaryKey().toString());
 309  
             }
 310  4
             if (constraints.isUnique() != null) {
 311  2
                 constraintsElement.setAttribute("unique", constraints.isUnique().toString());
 312  
             }
 313  
 
 314  4
             if (constraints.getUniqueConstraintName() != null) {
 315  0
                 constraintsElement.setAttribute("uniqueConstraintName", constraints.getUniqueConstraintName());
 316  
             }
 317  
 
 318  4
             if (constraints.getPrimaryKeyName() != null) {
 319  0
                 constraintsElement.setAttribute("primaryKeyName", constraints.getPrimaryKeyName());
 320  
             }
 321  
 
 322  4
             if (constraints.getPrimaryKeyTablespace() != null) {
 323  0
                 constraintsElement.setAttribute("primaryKeyTablespace", constraints.getPrimaryKeyTablespace());
 324  
             }
 325  4
             element.appendChild(constraintsElement);
 326  
         }
 327  
 
 328  13
         return element;
 329  
     }
 330  
 
 331  
     public Element createNode(ChangeSet changeSet) {
 332  0
         Element node = currentChangeLogFileDOM.createElementNS(XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(),
 333  
                 "changeSet");
 334  0
         node.setAttribute("id", changeSet.getId());
 335  0
         node.setAttribute("author", changeSet.getAuthor());
 336  
 
 337  0
         if (changeSet.isAlwaysRun()) {
 338  0
             node.setAttribute("runAlways", "true");
 339  
         }
 340  
 
 341  0
         if (changeSet.isRunOnChange()) {
 342  0
             node.setAttribute("runOnChange", "true");
 343  
         }
 344  
 
 345  0
         if (changeSet.getFailOnError() != null) {
 346  0
             node.setAttribute("failOnError", changeSet.getFailOnError().toString());
 347  
         }
 348  
 
 349  0
         if (changeSet.getContexts() != null && changeSet.getContexts().size() > 0) {
 350  0
             StringBuffer contextString = new StringBuffer();
 351  0
             for (String context : changeSet.getContexts()) {
 352  0
                 contextString.append(context).append(",");
 353  
             }
 354  0
             node.setAttribute("context", contextString.toString().replaceFirst(",$", ""));
 355  
         }
 356  
 
 357  0
         if (changeSet.getDbmsSet() != null && changeSet.getDbmsSet().size() > 0) {
 358  0
             StringBuffer dbmsString = new StringBuffer();
 359  0
             for (String dbms : changeSet.getDbmsSet()) {
 360  0
                 dbmsString.append(dbms).append(",");
 361  
             }
 362  0
             node.setAttribute("dbms", dbmsString.toString().replaceFirst(",$", ""));
 363  
         }
 364  
 
 365  0
         if (StringUtils.trimToNull(changeSet.getComments()) != null) {
 366  0
             Element commentsElement = currentChangeLogFileDOM.createElementNS(
 367  
                     XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "comment");
 368  0
             Text commentsText = currentChangeLogFileDOM.createTextNode(changeSet.getComments());
 369  0
             commentsElement.appendChild(commentsText);
 370  0
             node.appendChild(commentsElement);
 371  
         }
 372  
 
 373  0
         for (Change change : changeSet.getChanges()) {
 374  0
             node.appendChild(createNode(change));
 375  
         }
 376  0
         if (changeSet.getRollBackChanges() != null && changeSet.getRollBackChanges().length > 0) {
 377  0
             Element rollback = currentChangeLogFileDOM.createElement("rollback");
 378  0
             for (Change change : changeSet.getRollBackChanges()) {
 379  0
                 rollback.appendChild(createNode(change));
 380  
             }
 381  0
             node.appendChild(rollback);
 382  
         }
 383  
 
 384  0
         return node;
 385  
     }
 386  
 
 387  
     public Element createNode(CustomChangeWrapper change) {
 388  0
         Element customElement = currentChangeLogFileDOM.createElementNS(
 389  
                 XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "custom");
 390  0
         customElement.setAttribute("class", change.getClassName());
 391  
 
 392  0
         for (String param : change.getParams()) {
 393  0
             Element paramElement = currentChangeLogFileDOM.createElementNS(
 394  
                     XMLChangeLogSAXParser.getDatabaseChangeLogNameSpace(), "param");
 395  0
             paramElement.setAttribute("name", param);
 396  0
             paramElement.setAttribute("value", change.getParamValues().get(param));
 397  
 
 398  0
             customElement.appendChild(paramElement);
 399  0
         }
 400  
 
 401  0
         return customElement;
 402  
     }
 403  
 
 404  
     /*
 405  
      * Creates a {@link String} using the XML element representation of this change
 406  
      * 
 407  
      * @param node the {@link Element} associated to this change
 408  
      * 
 409  
      * @param buffer a {@link StringBuffer} object used to hold the {@link String} representation of the change
 410  
      */
 411  
     private void nodeToStringBuffer(Node node, StringBuffer buffer) {
 412  0
         buffer.append("<").append(node.getNodeName());
 413  0
         SortedMap<String, String> attributeMap = new TreeMap<String, String>();
 414  0
         NamedNodeMap attributes = node.getAttributes();
 415  0
         for (int i = 0; i < attributes.getLength(); i++) {
 416  0
             Node attribute = attributes.item(i);
 417  0
             attributeMap.put(attribute.getNodeName(), attribute.getNodeValue());
 418  
         }
 419  0
         for (Map.Entry entry : attributeMap.entrySet()) {
 420  0
             String value = (String) entry.getValue();
 421  0
             if (value != null) {
 422  0
                 buffer.append(" ").append(entry.getKey()).append("=\"").append(value).append("\"");
 423  
             }
 424  0
         }
 425  0
         buffer.append(">").append(StringUtils.trimToEmpty(XMLUtil.getTextContent(node)));
 426  0
         NodeList childNodes = node.getChildNodes();
 427  0
         for (int i = 0; i < childNodes.getLength(); i++) {
 428  0
             Node childNode = childNodes.item(i);
 429  0
             if (childNode instanceof Element) {
 430  0
                 nodeToStringBuffer(((Element) childNode), buffer);
 431  
             }
 432  
         }
 433  0
         buffer.append("</").append(node.getNodeName()).append(">");
 434  0
     }
 435  
 
 436  
 }