View Javadoc

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