Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eclipse EMF: Customize XML deserialization so old project can be loaded into modified model

Situation

I have an Eclipse RCP application that manages application projects within an EMF model.

These projects are saved by serializing them to the XMI format. Such files can then be loaded back into the model. I use the standard EMF tools (such as Resource) for this.

Due to model refactoring, the following has changed:

  • Old model
    • EClass MyClass with an attribute Name (with capital letter).
    • XMI: <MyClass Name="My Class Name 1" ... />

vs.

  • New model
    • EClass MyClass inherits from MyBaseClass, with attribute name (without capital letter).
    • EClass MyClass no longer has Name attribute, since EMF does not allow both. This makes sense as it would collide on e.g. the getter method getName().

Problem

How can I load an old XMI project file into my new model?

Up until this problem I was able to either:

  • avoid modifying the model
  • grow the model to contain both the old and new structures and perform modification after loading the project file: moving information from old to new types, updating references,....

In this case, however, I cannot load the XMI file in the first place: the model misses attribute name on one hand and does not recognize (and thus ignores) attribute Name on the other.

Question

What is the correct place to implement this backwards compatibility support?

I assume I should work on the deserialization process or the XML mapping.

Constraints for the solution are:

  • New projects (containing <MyClass name="..." ... />) must be loaded correctly as well.
  • Saving (i.e. serializing) a project model should always happen in the new format!
like image 336
tbacker Avatar asked Oct 20 '22 23:10

tbacker


1 Answers

This question was resolved by Ed Merks on the EMF forums.

Background

The cleanest way to support backward compatibility by intercepting the XML mapping, is by enabling an implementation of ExtendedMetaData on your EMF resource. This class is the central entry point for tweaking EMF resources and their content. It avoids having to specialize various other classes within the EMF framework.

However, my project already had a specialized XMLHelper class, which handles the XML serialization/deserialization, so Ed Merks helped solve my issue within that class.

Note that the XMI XMLHelperImpl source code shows how the ExtendedMetaData tool is called when it is enabled on a resource!

Solution using custom XMLHelper

/**
 * Helper class that allows intercepting the XML to model mapping, to support backwards compatibility.
 * <p>
 * 2 methods must be overridden to handle compatibility mappings:
 * <dl>
 * <dt>{@link XMLHelperImpl#getFeature(EClass, String, String, boolean)}</dt>
 * <dd>Is called to map features of a certain EClass. These include attributes and child elements in the XML file.</dd>
 * <dt>{@link XMLHelperImpl#getType(EFactory, String)}</dt>
 * <dd>Is called to map types that are used in the model.</dd>
 * </dl>
 * <p>
 * Their difference becomes clear by looking at the model file. Sometimes both need to be handled. For example:
 * <ul>
 * <li>a {@link Person} has zero or more {@link Person#getPhoneNumber()} configurations ('feature')</li>
 * <li>these features are of type {@link PhoneNumber} or possibly a subclass! ('type')</li>
 * </ul>
 * <p>
 * See https://www.eclipse.org/forums/index.php/m/1449615/
 */
public class CustomXmlHelper extends XMLHelperImpl implements XMLHelper {

    public CustomXmlHelper() {
        super();
        deresolve = true;
    }

    public CustomXmlHelper(XMLResource resource) {
        super(resource);
        deresolve = true;
    }

    @Override
    public EStructuralFeature getFeature(EClass eClass, String namespaceURI, String name, boolean isElement) {
        String compatName = name;
        if (eClass == ProjectModelPackage.Literals.MyClass) {
            if (!isElement && "Name".equals(name)) {
                 // 1.x to 2.x compatibility (October 2014)
                 //   1.x = MyClass attribute 'Name'
                 //   2.x = MyBaseClass attribute 'name', shared by MyClass
                 compatName = ProjectModelPackage.Literals.EMY_BASE_CLASS__NAME.getName(); // 'n(!)ame'
            }
        }
        // future feature mappings handled here
        return super.getFeature(eClass, namespaceURI, compatName, isElement);
    }

    @Override
    public EClassifier getType(EFactory eFactory, String name) {
    String compatName = name;
        if (eFactory == ProjectModelPackage.eINSTANCE) {
            // placeholder for type compatibility
            //            if ("OldTypeName".equals(name)) {
            //                compatName = ProjectModelPackage.Literals.NEW_TYPE_NAME.getName();
            //            }
        }
        return super.getType(eFactory, compatName);
    }
}
like image 87
tbacker Avatar answered Oct 22 '22 13:10

tbacker