Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » EMF » Customize XML deserialization so old project can be loaded into modified model
Customize XML deserialization so old project can be loaded into modified model [message #1449416] Tue, 21 October 2014 10:16 Go to next message
Eclipse UserFriend
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 than 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" ... />


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 name and does not recognize (and ignores) Name.

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 a project model should always happen in the new format!


I know classes such as XMLHelper and XMLMap exist, but these contain a lot of methods and I don't find their documentation self-explaining.

Thanks a lot!
Re: Customize XML deserialization so old project can be loaded into modified model [message #1449502 is a reply to message #1449416] Tue, 21 October 2014 13:02 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33140
Registered: July 2009
Senior Member
Tim,

There are several possible approaches. One approach would be to use
org.eclipse.emf.ecore.xmi.XMLResource.OPTION_RECORD_UNKNOWN_FEATURE so
that unrecognized content is recorded. The could then be
post-processed to recover/transform the information, perhaps uses
org.eclipse.emf.ecore.xmi.XMLResource.ResourceHandler. Is what we're
using in Oomph. Our resource factory is like this:

public Resource createResource(URI uri)
{
BaseResourceImpl result = new BaseResourceImpl(uri);
result.setEncoding("UTF-8");

Map<Object, Object> defaultLoadOptions =
result.getDefaultLoadOptions();
defaultLoadOptions.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE,
Boolean.TRUE);
defaultLoadOptions.put(XMLResource.OPTION_RECORD_ANY_TYPE_NAMESPACE_DECLARATIONS,
Boolean.TRUE);
defaultLoadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA,
Boolean.TRUE); <-- This is important for your example.
defaultLoadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING,
Boolean.TRUE); <-- This is important for your example because XMI
doesn't care about element verses attribute.
defaultLoadOptions.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION,
Boolean.TRUE);

Map<Object, Object> defaultSaveOptions =
result.getDefaultSaveOptions();
defaultSaveOptions.put(XMLResource.OPTION_SKIP_ESCAPE_URI,
Boolean.FALSE);
defaultSaveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA,
Boolean.TRUE);
defaultSaveOptions.put(XMLResource.OPTION_LINE_WIDTH, 10);
defaultSaveOptions.put(XMLResource.OPTION_SCHEMA_LOCATION,
Boolean.TRUE);

return result;
}

We specialize the resource implementation to create a specialized
extended metadata.

public void doLoad(InputStream inputStream, Map<?, ?> options) throws
IOException
{
if (options.get(OPTION_EXTENDED_META_DATA) instanceof Boolean)
{
ResourceSet resourceSet = getResourceSet();
Map<Object, Object> effectiveOptions = new HashMap<Object,
Object>(options);
effectiveOptions.put(OPTION_EXTENDED_META_DATA,
new BasicExtendedMetaData(resourceSet == null ?
EPackage.Registry.INSTANCE : resourceSet.getPackageRegistry())
{
@Override
public EStructuralFeature getElement(EClass eClass, String
namespace, String name)
{
EStructuralFeature eStructuralFeature =
super.getElement(eClass, namespace, name);
if (eStructuralFeature == null)
{
eStructuralFeature = super.getElement(eClass,
namespace, name.substring(0, name.length() - 1));
}

if (eStructuralFeature == null)
{
eStructuralFeature = eClass.getEStructuralFeature(name);
}

return eStructuralFeature;
}
});
options = effectiveOptions;
}

super.doLoad(inputStream, options);
}

In this case, we added extended metadata to the model so that plural
features are serialized with a singular name. But we want to read old
serializations using the plural name. So this "hack" let's us read in
the old serialization. In your case, you'd want to specialize
org.eclipse.emf.ecore.util.BasicExtendedMetaData.getAttribute(EClass,
String, String) because you're dealing with special handling for an
attribute in the XML.


On 21/10/2014 2:44 PM, Tim De Backer wrote:
> 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 than 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" ... />
>
>
> 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 name and does not recognize (and ignores) Name.
>
> 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 a project model should always happen in the new format!
>
>
> I know classes such as XMLHelper and XMLMap exist, but these contain a
> lot of methods and I don't find their documentation self-explaining.
>
> Thanks a lot!


Ed Merks
Professional Support: https://www.macromodeling.com/
Re: Customize XML deserialization so old project can be loaded into modified model [message #1449615 is a reply to message #1449502] Tue, 21 October 2014 16:10 Go to previous messageGo to next message
Eclipse UserFriend
Hi Ed,

Thanks for your reply!

Unfortunately, I am just as confused as before.

Wont setting all your options break a things? Why do I need to set save options if I only want to change the loading process? Why do you set a boolean as option value first and only override it with the BasicExtendedMetaData later? Why do I need OPTION_LAX_FEATURE_PROCESSING specifically?

Historically, the resource loading and saving in my application has been modified (hacked) tons of times, predating my arrival. We have a custom XMLHelper, we override XMIResourceImpl::getEObject(List<String> list), etc.

I am trying to clean all that up and support (current and future) backward compatibility between old XML serializations and new model objects in a single place as follows:
someMethod() {
    // handle MyClass compatibility: Name is mapped to name
   ...
   // handle some future compatibility
   ...

   <continue deserialization>
}


You'll understand I only want to add the extended MetaData thing in the mix if it has a major advantage. And I would like to get why that is the way to go and why that specific method getElement must be overridden to cover all cases.

Thanks a lot!
Re: Customize XML deserialization so old project can be loaded into modified model [message #1449623 is a reply to message #1449615] Tue, 21 October 2014 16:23 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33140
Registered: July 2009
Senior Member
Tim,

Comments below.

On 21/10/2014 6:10 PM, Tim De Backer wrote:
> Hi Ed,
>
> Thanks for your reply!
> Unfortunately, I am just as confused as before.
> Wont setting all your options break a things?
That's not the plan, no.
> Why do I need to set save options if I only want to change the loading
> process?
Sorry, I should have highlighted the load options as important.
> Why do you set a boolean as option value first and only override it
> with the BasicExtendedMetaData later?
Because only when the resource loads does it know the resource set and
uses that resource set's package registry; that subtlety probably isn't
important for you, but it is if the resource can't refer to dynamic
Ecore packages.
> Why do I need OPTION_LAX_FEATURE_PROCESSING specifically?
Because XMI treats elements and attributes as interchangeable. So by
setting this option, it will look for either an element or an attribute
with the matching name.
>
> Historically, the resource loading and saving in my application has
> been modified (hacked) tons of times, predating my arrival. We have a
> custom XMLHelper, we override XMIResourceImpl::getEObject(List<String>
> list), etc.
Then you might specialize
org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl.getFeature(EClass, String,
String, boolean) instead.
>
> I am trying to clean all that up and support (current and future)
> backward compatibility between old XML serializations and new model
> objects in a single place as follows:
>
> someMethod() {
> // handle MyClass compatibility: Name is mapped to name
> ...
> // handle some future compatibility
> ...
>
> <continue deserialization>
> }
>
>
> You'll understand I only want to add the extended MetaData thing in
> the mix if it has a major advantage.
It avoids specializing a bunch of other code, but if you have a
specialized XMLHelperImpl already you can see how it does the delegation
to the extended metadata if present and of course you can add your hooks
there.
> And I would like to get why that is the way to go and why that
> specific method getElement must be overridden to cover all cases.
You'd only check in either of these places with something like if
(eClass == MyModel.Literals.MY_CLASS && "Name".equals(name))return
MyModel.Literals.MY_CLASS__NAME and let all other cases proceed as normal.
>
> Thanks a lot!


Ed Merks
Professional Support: https://www.macromodeling.com/
Re: Customize XML deserialization so old project can be loaded into modified model [message #1450224 is a reply to message #1449623] Wed, 22 October 2014 08:13 Go to previous messageGo to next message
Eclipse UserFriend
Hi Ed,

Works like a charm. Thanks a lot!

One last issue (for future reference): does this same approach work for children in the XML file as well?

Say MyClass has one or more child elements MyChildClass, but in the model this has been renamed to MyBabyClass.

Will "MyChildClass" be the feature name in the method below, just like the "Name" (the attribute) was?

Is this what the LAX attribute was about?

Is the approach identical (i.e. overriding with "MyBabyClass" and moving on)?

For anyone else, these are my code snippets. Smile

class MyResourceFactoryImpl {
    public MyResource createResource(URI uri) {
        MyResource resource = new MyResource(uri);
        // options required for backwards compatibility support
        // see MyXmlHelper
        // see https://www.eclipse.org/forums/index.php/m/1449615/
        resource.getDefaultLoadOptions().put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, Boolean.TRUE);
        resource.getDefaultLoadOptions().put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
        return resource;
    }
}


class MyXmlHelper {
    /*
     * Method is overridden to include backwards compatibility support to load old projects into a new model.
     * When we need to intercept before the XML is loaded into the model, which happens here.
     * See https://www.eclipse.org/forums/index.php/m/1449615/
     * 
     * (non-Javadoc)
     * @see org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl#getFeature(org.eclipse.emf.ecore.EClass, java.lang.String, java.lang.String, boolean)
     */
    @Override
    public EStructuralFeature getFeature(EClass eClass, String namespaceURI, String name, boolean isElement) {
        String compatName = name;
        if (eClass == MymodelPackage.Literals.MyClass) {
            if ("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 = MymodelPackage.Literals.MY_BASE_CLASS__NAME.getName(); // 'n(!)ame'
            }
        }
        return super.getFeature(eClass, namespaceURI, compatName, isElement);
    }
}
Re: Customize XML deserialization so old project can be loaded into modified model [message #1450237 is a reply to message #1450224] Wed, 22 October 2014 08:23 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33140
Registered: July 2009
Senior Member
Tim,

Comments below.

On 22/10/2014 10:13 AM, Tim De Backer wrote:
> Hi Ed,
>
> Works like a charm. Thanks a lot!
> One last issue (for future reference): does this same approach work
> for children in the XML file as well?
>
> Say MyClass has one or more child elements MyChildClass, but in the
> model this has been renamed to MyBabyClass.
Class names are only used on the root objects of the resource (for XMI)
and in xmi/xsi:type.
> Will "MyChildClass" be the feature name in the method below, just like
> the "Name" (the attribute) was?
No, in general the element and attribute names derive from feature names
(except for the root object).
> Is this what the LAX attribute was about?
No, it's more about the fact that extended metadata can mark the
feature's kind as element/attribute and in XML Schema element and
attribute names can collide so it's important to allow the same name to
be used for each purpose.
>
> Is the approach identical (i.e. overriding with "MyBabyClass" and
> moving on)?
The EClass is computed by
org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl.getType(EFactory, String),
so here you can add a hook that maps from an old name to a new name;
this too delegates to extended metadata
org.eclipse.emf.ecore.util.ExtendedMetaData.getType(EPackage, String) if
that option is present.
> For anyone else, these are my code snippets. :)
>
>
> class MyResourceFactoryImpl {
> public MyResource createResource(URI uri) {
> MyResource resource = new MyResource(uri);
> // options required for backwards compatibility support
> // see MyXmlHelper
> // see https://www.eclipse.org/forums/index.php/m/1449615/
> resource.getDefaultLoadOptions().put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE,
> Boolean.TRUE);
> resource.getDefaultLoadOptions().put(XMLResource.OPTION_LAX_FEATURE_PROCESSING,
> Boolean.TRUE);
> return resource;
> }
> }
>
>
>
> class MyXmlHelper {
> /*
> * Method is overridden to include backwards compatibility support
> to load old projects into a new model.
> * When we need to intercept before the XML is loaded into the
> model, which happens here.
> * See https://www.eclipse.org/forums/index.php/m/1449615/
> * * (non-Javadoc)
> * @see
> org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl#getFeature(org.eclipse.emf.ecore.EClass,
> java.lang.String, java.lang.String, boolean)
> */
> @Override
> public EStructuralFeature getFeature(EClass eClass, String
> namespaceURI, String name, boolean isElement) {
> String compatName = name;
> if (eClass == MymodelPackage.Literals.MyClass) {
> if ("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 =
> MymodelPackage.Literals.MY_BASE_CLASS__NAME.getName(); // 'n(!)ame'
> }
> }
> return super.getFeature(eClass, namespaceURI, compatName,
> isElement);
> }
> }
>


Ed Merks
Professional Support: https://www.macromodeling.com/
Re: Customize XML deserialization so old project can be loaded into modified model [message #1451165 is a reply to message #1450237] Thu, 23 October 2014 12:25 Go to previous message
Eclipse UserFriend
The difference is clear now!

I have inserted placeholder in the overridden getType method for possible future use.

Thanks a lot!
Previous Topic:Import XML model to EMF
Next Topic:NotificationImpl.wasSet() ignores the feature default value when it's null
Goto Forum:
  


Current Time: Thu Apr 25 00:22:38 GMT 2024

Powered by FUDForum. Page generated in 0.04662 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top