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;
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")) {
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")) {
186 continue;
187 }
188
189
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
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
406
407
408
409
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 }