Multi-tier Migration

You are viewing an old version (v. 17) of this page.
The latest version is v. 22, last edited on Feb 11, 2010 (view differences | )
<< View previous version | view page history | view next version >>

Migrate super class and sub class separately

Continue with version "1" of the Task class defined in Getting Started chapter:

package com.pmease.commons.xmt.bean;

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".
When deserializing the compile task object from XML, XMT splits this composite version to get XML version for each class in the hierarchy, and repeats the process described in Getting Started chapter for each of these classes. So if class Task is evolved to use numeric priority field, we simply add migrate methods in Task class, while keep CompileTask class intacted. On the other hand, if we evolve class CompileTask to include a destDir field, we can define the migrate method in CompileTask like below, while keep class Task intacted:

package example;

import java.util.List;
import java.util.Stack;

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 problem

Now 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:

  1. Split "1.0" into a versions list containing element "0" and "1" (version of sub class stored as first element). Current class is set to CompileTask.
  2. Remove the first element in the versions list to get version "0", compare it with current version of current class. This results in execution of method migrate1, with versions param pointing to current versions list. After this step, current class is switched to the super class AbstractCompileTask.
  3. XMT continues to remove the first element in versions list, and get value of the removed element, which is now "1", and compare it with current version of current class. This results in a class mismatch since version "1" recorded in XML is derived from class Task, but the current class is AbstractCompileTask.

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.addElement("options").setText("-debug");
        }
}

This new migrate method inserts version "0" (current version of class AbstractCompileTask) into versions list, and the list now contains element "0", and "1" to handle data migration of class AbstractCompileTask and Task respectively. 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 need to write AbstractCompileTask as below (assume class Task is at version "2" when it is removed):

package example;

import java.util.Stack;
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);
	}
        
        private 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 first removes the first element from versions param, which is the version intended to

When migrate from aforementioned "1.0" XML
With this modification, XMT will then follow below procedure to migrate from the aforementioned XML:

  1. It splits version "0.0" recorded in XML as a list, with the first element being version of CompileTask and second element being version of Task.
  2. It
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.