Data Migration Tool
|
Release of XMT is managed under QuickBuild, a continuous integration and release management tool. Get your free community edition here.
Migrate super class and sub class separatelyContinue with version "1" of the Task class defined in Getting Started chapter: package example; import java.util.Stack; import org.dom4j.Element; import com.pmease.commons.xmt.VersionedDocument; public class Task { enum Priority {HIGH, MEDIUM, LOW} public Priority priority; @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { Element element = dom.getRootElement().element("prioritized"); element.setName("priority"); if (element.getText().equals("true")) element.setText("HIGH"); else element.setText("LOW"); } } Now we have class CompileTask subclassing from Task class as below: package example; import java.util.List; public class CompileTask extends Task { public List<String> srcFiles; } Instance of CompileTask can be serialized to XML with XMT as below: package example; import java.util.ArrayList; import com.pmease.commons.xmt.VersionedDocument; public class Test { public static void main(String args[]) { CompileTask task = new CompileTask(); task.priority = Task.Priority.HIGH; task.srcFiles = new ArrayList<String>(); task.srcFiles.add("Class1.java"); task.srcFiles.add("Class2.java"); String xml = VersionedDocument.fromBean(task).toXML(); saveXMLToFileOrDatabase(xml); } private static void saveXMLToFileOrDatabase(String xml) { // save XML to file or database here } } The resulting XML will be:
<example.CompileTask version="1.0">
<priority>HIGH</priority>
<srcFiles>
<string>Class1.java</string>
<string>Class2.java</string>
</srcFiles>
</example.CompileTask>
Pay attention to version attribute of the root element: XMT examines the class hierarchy (except for class java.lang.Object) to get current version of each class, and concatenates them with period. Since class Task is of version "1" and class CompileTask is of version "0", the resulting version of the hierarchy (or composite version) is "1.0". package example; import java.util.List; import java.util.Stack; import com.pmease.commons.xmt.VersionedDocument; public class CompileTask extends Task { public List<String> srcFiles; public String destDir = "classes"; @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { dom.getRootElement().addElement("destDir").setText("classes"); } } This separation of concerns is very important since there might exist many sub classes, and you certainly do not want to modify those sub classes if super class is evolved, and vice versa. Address class hierarchy change problemNow we introduce class AbstractCompileTask in the middle of Task and CompileTask like below: package example; public abstract class AbstractCompileTask extends Task { public String options = "-debug"; } package example; import java.util.List; import java.util.Stack; public class CompileTask extends AbstractCompileTask { public List<String> srcFiles; public String destDir = "classes"; @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { dom.getRootElement().addElement("destDir").setText("classes"); } } When deserialize from aforementioned "1.0" XML, XMT does the following:
This issue can be solved by introducing another migrate method into class CompileTask: package example; import java.util.List; import java.util.Stack; public class CompileTask extends AbstractCompileTask { public List<String> srcFiles; public String destDir = "classes"; @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { dom.getRootElement().addElement("destDir").setText("classes"); } @SuppressWarnings("unused") private void migrate2(VersionedDocument dom, Stack<Integer> versions) { versions.push(0); dom.getRootElement().addElement("options").setText("-debug"); } } This new migrate method pushes version "0" (current version of class AbstractCompileTask) into versions stack. The stack now contains element "0" and "1" from top to bottom, which can be used to handle data migration of class AbstractCompileTask and Task correctly. It also adds "options" element so that compile task objects deserialized from XML of old versions has the default compile options value of "-debug". Now that we've successfully handled the case of adding new class into the hierarchy, but how about removing an existing class? Let's assume that class Task is removed and class AbstractCompileTask needs to take care of the priority field. In order to deserialize from XML of old versions, we write AbstractCompileTask as below (assume class Task is at version "2" when it is removed): package example; import java.util.Stack; import org.dom4j.Element; import com.pmease.commons.xmt.VersionedDocument; import com.pmease.commons.xmt.MigrationHelper; public class AbstractCompileTask { public int priority; public String options = "-debug"; @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { int taskVersion = versions.pop(); MigrationHelper.migrate(String.valueOf(taskVersion), TaskMigrator.class, dom); } public static class TaskMigrator { @SuppressWarnings("unused") private void migrate1(VersionedDocument dom, Stack<Integer> versions) { Element element = dom.getRootElement().element("prioritized"); element.setName("priority"); if (element.getText().equals("true")) element.setText("HIGH"); else element.setText("LOW"); } @SuppressWarnings("unused") private void migrate2(VersionedDocument dom, Stack<Integer> versions) { Element element = dom.getRootElement().element("priority"); if (element.getText().equals("HIGH")) element.setText("10"); else if (element.getText().equals("MEDIUM")) element.setText("5"); else element.setText("1"); } } } The newly added migrate method pop up the top element from versions stack to get the version intended to migrate class Task, and call MigrationHelper.migrate to apply migration methods defined in class TaskMigrator as necessary. Migration methods defined in class TaskMigrator are simply copied from the deleted Task class. When migrate from aforementioned "1.0" XML with this new class hierarchy, XMT does the following:
|