EMF Forms Developer Tooling: View Model Migration

EMF Forms Developer Tooling: View Model Migration

EMF Forms makes it really simple to create forms in order to edit your data, based on an EMF model. To get started with EMF Forms please refer to our tutorial. We have recently released EMF Forms 1.5.0, which will also ship with the Eclipse Luna SR2 Modeling Tools. In this blog post, we describe another new feature of the EMF Forms Tooling, which support the migration of so called view models (see here for an introduction of view models). If you are a user of EMF Forms and you just want to learn how to get your view models migrated, see our migration guide.

EMF makes it simple for developers to create a model for their application data. The framework offers code generation, serialization, persistent storage, viewing and editing of the model instances as well as a rich ecosystem of frameworks. As usual, for any persistent data, one challenge of using EMF is migration. Due to changing/additional requirements or bugs it might be necessary to change the Ecore model at a later stage. These changes might potentially be breaking changes, so that serialised instances of the old model can’t be de-serialised using the updated model. If the application was already deployed to clients, you are in trouble. When using EMF Forms, you have typically two relevant models: one describing the data entities of the application and another describing the form-based views (view model). Luckily, there is a solution for both, in this blog post, we describe how we solved the migration for view models within the EMF Forms framework. However, the solution can of course also be transferred to your data entities.

Edapt to the Rescue

The project that tackles the migration problem is Edapt. Edapt consists of two main components: a tooling integration with the EMF Ecore-editor and a runtime component which allows one to de-serialise and migrate old model instances saved as XML/XMI files.

Edapt keeps a history of an Ecore which contains all changes made to the model in chronological order. The changes are further grouped into releases. A release is uniquely identified by a namespace-uri with a version of the Ecore model, for example https://library/v1 and https://library/v2.

When migrating a model instance, Edapt will parse the xml file for the namespace-uri and identify the source release in the associated history. Based on the source release it will create an in-memory metamodel to load the xml file. The in-memory representation of the metamodel and the model instance are then migrated to the latest release by applying the changes recorded in the history.

edapt.png

The tooling to create a history is easy to use, since it integrates with the default Ecore editor without problems. Any change made to Ecore will be tracked as a change in the history automatically. A change itself does only hold the information how to migrate the metamodel.

There are two ways to add the information on how to migrate a model instance:

Instead of editing the Ecore directly Edapt ships with an Operation Browser which allows one to perform common changes on the Ecore. This will automatically wrap the changes in an operation. Every operation will update the metamodel and the model instance. Let us assume you have to rename an EStructuralFeature. Using the rename operation the migration will keep the value of the feature and simply update the name in the in-memory representation of the instance.

edapt2.png

If there is no operation for your use case but if you want to handle the model migration in a different way, it is possible to add/attach custom migration. A custom migration is a java class which gets access to the in-memory representation of the metamodel and model instance. This is intended for more complex migrations. For example you might decide that an EClass has become too complex and you want to break it down to several more specialised subclasses. You can make the changes to the Ecore and wrap the changes in a custom migration. This allows one to analyse the existing model instances and based on their values you can migrate them to one of the subclasses.

public class MyCustomMigration extends CustomMigration {
	
	/** Migration that needs to be performed before the metamodel change. */
	public void migrateBefore(Model model, Metamodel metamodel) throws MigrationException {
		// use the yet unchanged metamodel to save values, etc
	}
	
	/** Migration that needs to be performed after the metamodel change. */
	public void migrateAfter(Model model, Metamodel metamodel) throws MigrationException {
		// perform the migration on the model using the updated metamodel
	}
}

A practical application of Edapt in EMF Forms

You might ask if this is applicable in practice or if it is just some academic exercise. It is not. We are actively using Edapt in the EMF Forms tooling beginning with release 1.5.0.

At the core of the EMF Forms Framework are view models. A view model describes the UI of a metamodel in a form-based way. The heart of every viewmodel is described by a core EMF-Model which contains all basic elements to model the UI. Furthermore there are multiple independent extensions to the core model which offer further elements.

For our recent release of EMF Forms we faced a migration problem, because of a change in one of the core extensions. Naturally our first step was to look at existing solutions and review the possibility to use Edapt (See this basic tutorial on Edapt).

Edapt allows to migrate model instances which have references to multiple Ecores. However this requires that there is one history containing all changes for every Ecore. Since a view model instance might have references to multiple independent (and potentially unknown) extensions, it is not possible for us to create such a history at development time.

Our Solution

Our approach in fixing this shortcoming was to delay the creation of this one history file to the runtime. For this to work every view model plugin provides its own history, which is registered at the regular Edapt extension point.

At runtime we parse the persisted view model files to get all namespace URIs of referenced Ecores using SAX. The URIs are then used to retrieve the migrator for every model bundle using Edapt’s Migrator Registry.

final Migrator migrator = MigratorRegistry.getInstance().getMigrator(nsUri);

The main use of the migrator is to offer API for executing the migration (#migrateAndSave and #migrateAndLoad). However it also gives access to the releases, which contain all recorded changes, operations and migrations.

Using this set of migrators we can create a new history by combining all releases. To make this work we introduced two conventions on our side:

  • Releases are labeled with the version they were shipped. This allows us to identify that Release 1.4.0 of Ecore A will be placed between releases 1.3.0 and 1.5.0 of Ecore B

  • When Ecore A is depending on Ecore B and both have a release for the same version, the release of Ecore B will be added before Ecore A.

Since Edapt is internally modelled with EMF, creating a history and adding the releases based on our conventions is straightforward:

final History history = HistoryFactory.eINSTANCE.createHistory();
history.getReleases().add(sourceRelease);
history.getReleases().addAll(targetReleases);

The new history can then be used to create a new migrator instance. From this point onwards, we can use the new migrator as usual:
final URI uri = tempSaveHistoryFile(history);
final Migrator migrator = new Migrator(uri, new CustomMigrationClassLoader());
final Release release = migrator.getRelease(0);
migrator.migrateAndSave(Collections.singletonList(resourceURI), release, null, new NullProgressMonitor());

EMFForms Tooling Integration

We integrated our approach to migrate view models using Edapt in our EMF Forms developer tooling with release 1.5.0. Please see our migration guide for a step by step description how to migrate your view models when updating to a new version of EMF Forms. Please note that you have to migrate your view models if you used a version previous to 1.5.1 and are updating to 1.5.1 now.