There are changes that can occur to the way that models are stored between versions of an application. The DevOps Modeling Platform uses a migration infrastructure to automatically migrate models when they are first opened. This is done transparently to the user although the user will be informed that a migration has been performed.
The migration infrastructure used by the UML Modeler allow various handlers to participate in the migration process including participants contributed by third-parties. The migration infrastructure can be used in domain modeling applications (see Using Migration in Domain Models for more details).
There are a number of different types of model changes that can occur between versions.
The following example illustrates some format changes to a model. Generally, these changes are indicated by a change to a schema (and it's namespace URI) or by a change in URI scheme (and its name). Migration participants can be registered against one of these two things to migrate the format of a model.
Before Format Changes:
<extlibrary:Librairie nom="myLibrary" ... xmlns:extlibrary="http:///org.eclipse.emf.extlibrary/1.0.0/" ... >
<livre titre="myBook">
<ecrivain href="file://c:/eclipse/workspace/myProject/otherLibrary.extlibrary#/"/>
</livre>
</extlibrary:Librairie>
After Format Changes:
<extlibrary:Library name="myLibrary" ... xmlns:extlibrary="http:///org.eclipse.emf.extlibrary/1.0.1/" ... >
<book title="myBook">
<writer href="platform:/resource/myProject/otherLibrary.extlibrary#/"/>
</book>
</extlibrary:Library>
Format changes are indicated by changes to namespace URI's and/or URI scheme names. Clients can register their migration participants against the old namespace URI's or URI scheme names in order to migrate the data into the newest format.
At a higher level, there can be changes to the model that don't directly affect the ability of the resource to load the model (ie. not a schema or scheme change) but there are changes in the way that the information is stored within the structure of the schema or scheme. Such changes could cause problems for the application because it might assume that data is organized in a particular way. The following example illustrates how a book that is contained within the library element in one version of the model becomes a sibling of the library element in the next version. Also, the URI points to a different location to find the writer of the book.
Before Content Changes:
<extlibrary:Library name="myLibrary" ... xmlns:extlibrary="http:///org.eclipse.emf.extlibrary/1.0.0/" ... >
<book title="myBook">
<writer href="platform:/resource/myProject/otherLibrary.extlibrary#/"/>
</book>
</extlibrary:Library>
After Content Changes:
<XMI ... xmlns:extlibrary="http:///org.eclipse.emf.extlibrary/1.0.0/" ... >
<extlibrary:Library name="myLibrary"/>
<extlibrary:Book title="myBook">
<writer href="platform:/resource/myOtherProject/yetAnotherLibrary.extlibrary#/"/>
</extlibrary:Book>
</XMI>
Changes to the model's file format at this higher level are captured by changes to the versions of signatures. Participants in the migration process are registered against an old signature version in order to migrate data to the newest version.
The com.ibm.xtools.emf.core.backwardCompatibility
extension point is used in conjunction with the
IRMPResource
to automatically migrate models when they are loaded and optionally preserve the older version when they are saved. Note:
the UML modeler does not currently support the ability to preserve the older version when saving its models but domain
modeling tools can choose to support this option.
When a schema changes, its namespace URI should also have changed. In order to migrate a model with a schema change the mapping from the old namespace URI must be mapped to the new namespace URI.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<Ecore2XML>
<schema nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0"/>
<schema nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.1"/>
</Ecore2XML>
</extension>
Along with the above mapping, often there is a need to map in a one-to-one fashion the metaclasses (EClasses) from the source schema to the destination schema. This is done using an EMF ecore2xml mapping file that can be provided as part of an Ecore2XML mapping like the following example.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<Ecore2XML file="mappings/libraryMappings.ecore2xml">
<schema nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0"/>
<schema nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.1"/>
</Ecore2XML>
</extension>
libraryMappings.ecore2xml:
<ecore2xml:XMLMap ... xmlns:ecore2xml="http://www.eclipse.org/emf/2005/Ecore2XML">
<ecoreToXMLInfo>
<key xsi:type="ecore:EClass" href="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.1#/Book"/>
<value name="Livre" targetNamespace="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0"/>
</ecoreToXMLInfo>
...
</ecore2xml:XMLMap>
For cases where where there are mappings that are not one-to-one then a
ResourceHandler
can be registered against the
old schema namespace URI so that it can handle these mappings. The ResourceHandler interface defines four methods:
preLoad, postLoad, preSave and postSave. The two methods that are used in the migration process are the postLoad
and preSave.
When postLoad is called on the resource handler, the resource will have partial contents (the content for which there was already a mapping). The information that was parsed out of the XML file but was not mapped is stored in the Resource's EObject to extension map. The resource handler is responsible for moving as much data out of the map and placing it into the resource contents as it can. The goal is to have moved all data out of the map after all of the resource handlers have been called. Otherwise, there will be data in the model that is not visible to the user and not modifiable by the user.
A resource handler can be called to perform a preSave. At this stage, it is supposed to do the opposite of the postLoad. Any data that does not have a reverse mapping must be moved back into the Resource's EObject to extension map in a form that will cause the XML to be saved as it would have been saved in the previous version.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<ResourceHandler class="com.ibm.foo.MyResourceHandler">
<schema nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0"/>
</ResourceHandler>
</extension>
MyResourceHandler.java:
public class MetaModelFixupResourceHandler implements XMLResource.ResourceHandler {
private static final String FRENCH_TITLE = "titre"; //$NON-NLS-1$
public void postLoad(XMLResource resource, InputStream inputStream, Map options) {
for (Iterator i = EcoreUtil.getAllProperContents(resource, false); i.hasNext();) {
EObject eObj = (EObject)i.next();
if (eObj.eClass() == EXTLibraryPackage.eINSTANCE.getBook()) {
Book b = (Book)eObj;
AnyType aType = (AnyType)resource.getEObjectToExtensionMap().get(b);
if (aType != null) {
for (ListIterator j = aType.getAnyAttribute().listIterator(); j.hasNext();) {
FeatureMap.Entry entry = (FeatureMap.Entry)j.next();
if (entry.getEStructuralFeature().getName().equals(FRENCH_TITLE)) {
b.setTitle((String)entry.getValue());
j.remove();
}
}
}
}
}
}
public void preSave(XMLResource resource, OutputStream outputStream, Map options) {
for (Iterator i = EcoreUtil.getAllProperContents(resource, false); i.hasNext();) {
EObject eObj = (EObject)i.next();
if (eObj.eClass() == EXTLibraryPackage.eINSTANCE.getBook()) {
Book b = (Book)eObj;
if (b.getTitle() != null) {
AnyType aType = (AnyType)resource.getEObjectToExtensionMap().get(b);
if (aType == null) {
aType = XMLTypeFactory.eINSTANCE.createAnyType();
resource.getEObjectToExtensionMap().put(b, aType);
}
EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
eAttribute.setName(FRENCH_TITLE);
eAttribute.setEType(XMLTypePackage.eINSTANCE.getAnySimpleType());
eAttribute.setDerived(true);
eAttribute.setTransient(true);
eAttribute.setVolatile(true);
aType.getAnyAttribute().add(eAttribute, b.getTitle());
}
b.setTitle(null);
}
}
}
}
When a URI scheme changes a new scheme name is chosen. This usually occurs when the structure of the URI has
drastically changed. The IURIHandler
below is registerd with the purpose of converting file URI's into eclipse
platform URI's for references that point to resources in the workspace. The structure of these two different types
of URI is quite different because while a file URI is rooted at the filesystem root directory a platform URI is
rooted at the logical workspace root.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<URIHandler class="com.ibm.foo.PlatformURIHandler">
<scheme name="file"/>
</URIHandler>
</extension>
PlatformURIHandler.java:
public class PlatformURIHandler implements IURIHandler {
public URI convert(URI original, IRMPResource resource) {
// See if this original URI points to something in the workspace
...
if (!isInWorkspace) {
// The contract for the URI handler is to return the original object if there is no change
// being made to the URI.
return original;
}
// Calculate the workspace path of this URI
...
return URI.createPlatformResourceURI(workspacePath, true);
}
public URI revert(URI newFormatURI, IRMPResource resource) {
// Find the filesystem path of this eclipse platform URI
...
return URI.createFileURI(fileSystemPath);
}
}
The following ResourceHandler is registered against an old signature version. The signature version change is used to indicate that a migration needs to be performed (by calling this ResourceHandler) even if the schemas have remained the same between the two versions of the model. The difference between this ResourceHandler and the one mentioned in a previous section is that this ResourceHandler should only be working with the resource contents, not the Resource's getEObjectToExtensionMap. By the time that the postLoad or preSave is called here there should not be any data in the extension map.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<ResourceHandler class="com.ibm.foo.MyResourceHandler">
<signature id="com.ibm.foo.someSignature" version="1.0"/>
</ResourceHandler>
</extension>
The following URI handler is registered against an old signature version rather than a scheme. Its purpose is to detect references to particular library models that have changed their location since the last version of the model (when the signature version was lower than the current version). This is not usually because of a change in the structure of the URI, just a change in its path.
<extension
point="com.ibm.xtools.emf.core.backwardCompatibility">
<URIHandler class="com.ibm.foo.MyURIHandler">
<signature id="com.ibm.foo.someSignature" version="1.0"/>
</URIHandler>
</extension>
The IRMPResource interface allows clients to detect whether a model was migrated and to control
whether to preserve the old format when saving the resource. When an IRMPResource is first loaded,
the isOlderArtifactVersion()
method
can be called by clients to determine if a migration had occurred. Clients can provide the
OPTION_PRESERVE_CONTENT_VERSION
option as a resource save option in order to force the resource to be saved back into the original format.
The UML modeler does not support the saving of the model back into the older version, so it does not use the presever option. However, it uses the isOlderArtifactVersion method in order to determine whether to present a dialog to user indicating that the file will be overwritten in the new format.
The RMPResourceFactory
is available for clients to create an IRMPResource. Alternatively, this factory can be registered
against the "org.eclipse.emf.ecore.extension_parser" extension point to ensure that EMF
uses this resource implementation along with its migration capabilities.
<extension
point="org.eclipse.emf.ecore.extension_parser">
<parser
class="com.ibm.xtools.emf.core.resource.RMPResourceFactory"
type="someFileExtension"/>
</extension>
Whenever an IRMPResource is saved, there must be at least one signature present. Otherwise, the resource will assume that it must be migrated the next time it is loaded.