Skip to content

The Model to Model Transformation Engine

Maxence Vanbésien edited this page Aug 7, 2013 · 2 revisions

Global Overview

This tool has been designed and implemented by the Worldline company, to propose a new way to manage Model-2-Model transformations.

You may already think that this kind of tool already exists in the Eclipse community or in the Open Source world (like ATL or XTend, for instance). But supplanting such frameworks or languages is clearly not the objective of this tool. In fact, rather than defining a new way to describe transitions, it will propose a new way for organizing them.

The purpose of this document is to describe the mechanisms that are used within this tool, that make it innovative, along with a handbook and best-practices to know how to use it properly.

What does a user need to provide to make Optimus work ?

Optimus is, at first, able to sequence and execute transformations for EObjects. As a reminder, an EObject is a java Object that comes from an EMF model (for more information about the EMF framework, please have a look at the EMF web page at http://www.eclipse.org/modeling/emf/ ). This means that this tool will be compliant, by default, with any EMF-based model.

The Optimus framework always takes two pieces of data as input. The first is a list of EObjects, and the second is a Context.

As you may have already guessed, the first piece of data consists in the elements on which the transformations will be executed. They usually come from a user selection or as output from another previously-executed process. This is the input list. From this input list will be computed another list, that will consist in all the elements that will be proposed to transformations. This new list, called eligible elements list, contains the elements from the input list, along with their direct parents and all their children.

The picture below describes, from a user selection, what will be the result of the resolution of eligible elements.

img1

The purpose and usage of the Context object will be described in a next part.

About the transformation definition

One transformation will be split into two distinct java objects. A Transformation Factory and the Transformation itself

The Transformation Factory class

The purpose of the Transformation Factory is double.

At first, this Transformation Factory contains the implementation, that checks if the associated Transformation can be applied on a given EObject. This is the notion of eligibility. In a second time, if the provided EObject is eligible, this Transformation Factory will create a new Transformation instance for it.

Please note that a given transformation instance will be strongly linked to a given EObject. Thanks to this, the Optimus internal engine will know whether a given transformation exists and whether it has already been executed for a given EObjects. This way, a given transformation for a given EObject cannot be executed twice.

In a technical point of view, the Transformation Factory implementation needs to implement the ITransformationFactory interface, that will require the implementation of two methods shown on the picture below:

img2

where:

  • The isEligible method contains the eligibility condition implementation
  • The createTransformation method contains the Transformation creation implementation

The example hereafter shows the implementation of a transformation factory that should add an Entity Annotation on a UML class that holds the <> UML Stereotype. ` import net.atos.optimus.m2m.engine.transformations.ITransformationFactory; import net.atos.optimus.m2m.engine.transformations.AbstractTransformation;

import org.eclipse.emf.ecore.EObject; import org.eclipse.uml2.uml.Classifier; import org.eclipse.uml2.uml.Element;

public class T11EntityAnnotationFactory implements ITransformationFactory {

public boolean isEligible(EObject eObject) {
       return eObject instanceof Classifier && ((Element) eObject).getAppliedStereotype("Domain::entity") != null;
}

public AbstractTransformation<Classifier> create(EObject eObject, String id) {
       return new T11EntityAnnotation((Classifier) eObject, id);
}

} `

The transformation class

The purpose or the transformation class is to contain the implementation of the transformation itself.

It is very important to understand that there won't be only one instance of Transformation for all the objects processed by the Transformation engine. Indeed, to ensure that a given transformation cannot be applied twice for a same object, one instance of a given transformation will be created and cached for each eligible EObject, within the Transformation Engine. (This is also why we needed to define a factory for it).

The transformation class can be parametrized with an EObject instance, to ensure a strong link with the instance of object that is being transformed. A transformation class should extend the AbstractTransformation abstract class provided by the Optimus framework, as it is described on the picture below:

img3

As you can see here, this class can be parametrized with an EObject. This ensures a strong link with a given type. You can also see that the inheritance to this class requires the implementation of a transform method, which takes a Transformation Context as parameter. This context is the one that was mentioned previously in the document, and that will be presented later.

The example hereafter shows an example of transformation class, that adds an @Entity annotation to a java class. ` import net.atos.optimus.m2m.engine.transformations.AbstractTransformation; import net.atos.optimus.m2m.engine.transformations.ITransformationContext;

import org.eclipse.gmt.modisco.java.Annotation; import org.eclipse.gmt.modisco.java.ClassDeclaration;

public class T11EntityAnnotation extends AbstractTransformation {

public T11EntityAnnotation(Classifier eObject, String id) {
     super(eObject, id);
}

@Override
public void transform(ITransformationContext context) {
     //Retrieve the class declaration corresponding to current class, in context
     ClassDeclaration classDeclaration = (ClassDeclaration) context.get(getEObjectFragment(), "self");
      classDeclaration.getAnnotations().add(this.getEntityAnnotation());
}

} `

The transformation registration

Now we have defined a new transformation, we need to let it be known by Optimus.

The registration process is done through Eclipse extension points, that are specified within the plugin.xml file of the plugin project in which you are implementing your transformation.

The picture below describes the extension point structure, as a UML diagram.

img4 All the modeled datatypes correspond to Java implementations that are described in the whole document.

The Transformation Set definition

The proposed extension point requires the creation of transformation sets. These transformation sets can be seen as containers of transformations. They are used to organize the transformations according to their own scopes. Moreover, those scopes can be used during the Optimus execution, to limit the number of processed transformations. Hence, the transformation sets are enough separate each transformation process.

In the extension point definition, the Transformation Set definition requires an ID, and an implementation class, as described on the picture below:

img4

This implementation class should extend the TransformationSet abstract class (provided by the framework), and is defined to apply another level of eligibility on EObjects (For instance, verify in a transformation set, the availability of a specific nature on the project, to know whether to enable or disable its transformations).

Hereafter is an example of Transformation set implementation, checking if the project that contains the EObject has the specific nature :

` import net.atos.optimus.m2m.engine.transformations.TransformationSet;

import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;

public class DomainTransformationSet extends TransformationSet {

@Override
public boolean isEligible(EObject eObject) {

      IFile file = WorkspaceSynchronizer.getFile(eObject.eResource());
      IProject project = file.getProject();
     try {
          return project.getNature("net.atos.mynature") != null;
     } catch (CoreException e) {
           logException(e);
     }
     return false;
}

} `

The transformation definition

The transformation registration in the extension point requires 4 pieces of information.

* **The ID** : Should be unique across all the transformations referenced in the workspace. The ID is essentially used internally, and need to be a static and user-specified value, since this id value can be reinvest to override a given transformation.

* **The description** : This information will be displayed in the Eclipse Preferences UI and in the logs. This data is for information only.

* **The priority** : If not specified, the value is O. The purpose of this value is for the requirement definition system, and for transformation overriding (since defining another new transformation with same ID ad this one and with higher priority will supplant the current one - More information about extensibility to come later).

* **The factory**: Should contain the fully qualified name of the factory implementation related to this transformation.

The requirement definition

As already said in a previous part, the chaining of all the transformations is guaranteed thanks to the definition & the management of requirements.

A requirement corresponds to the definition of another transformation that has to be executed before the current transformation, to ensure that this current transformation can be run safely with the right initial conditions. For instance, let's imagine that a transformation consists in the addition of a specific java annotation on a java class. A requirement to this transformation would be the creation of this same java class.

The definition of such requirement consists in providing, in the extension point, the ID of the required transformation, along with a list of objects, on which the required transformation should be applied.

By default, three types of requirements are provided:

* **The Object Requirement** : This requirement will apply the required transformation (identified by an id) to the object that is being transformed by the current transformation.

* **The Parent Requirement** : This requirement will apply the required transformation on the parent of the object being transformed y the current transformation.
  • The Custom Requirement : This requirement will apply the required transformation on a list of elements that is to be provided by the user. This list of element will be computed from the current object, based on an algorithm to be written by the user itself. The implementation (which fully qualified name is referenced in the extension point) has to extend the AbstractRequirement class, provided by the framework. In this implementation, the user will have to implement the List getMatchingEObjects(EObject eObject, ITransformationContext context) method.

Internally, the Object and the Parent requirements are specific implementation of the Custom requirements, that are provided by the framework.

The example below describes the implementation of such custom requirement, which purpose is to ensure that, for the generation or a role of an association, the class which should contain this role exists.

` import java.util.Collections; import java.util.List;

import net.atos.optimus.m2m.engine.requires.AbstractRequirement; import net.atos.optimus.m2m.engine.transformations.ITransformationContext;

import org.eclipse.emf.ecore.EObject; import org.eclipse.uml2.uml.Association; import org.eclipse.uml2.uml.Property; import org.eclipse.uml2.uml.Type;

public class AssociationClassRequirement extends AbstractRequirement {

@Override
public List<Type> getMatchingEObjects(EObject eObject, ITransformationContext context) {
     if (!(eObject instanceof Property))
          return null;

      Property property = (Property) eObject;
     if (property.getAssociation() == null)
          return null;

      Association association = property.getAssociation();
     if (association.getMemberEnds().size() != 2)
          return null;

      Property p = association.getMemberEnds().get(0).equals(property) ? association.getMemberEnds().get(1) : association.getMemberEnds().get(0);

     return Collections.singletonList(p.getType());
}

}

The Context usage

When executing transformations, you generally need a way to easily find information resulting from previous transformations. This ease can be brought by proposing a way to match elements from input models with elements from output models. Doing this is the role of the Context.

The Context is a structure that proposes a way to store elements, as follows.

At first, this context will be able to store root elements. The storage of root elements is a facility that is often used outside of the transformations scope. This means that, once the execution of Optimus is finished, it is able to retrieve the root elements from the context, to provide them to the next step of a more global process. Moreover, root elements are identified programmatically by a keyword, to help the user distinguishing a given root from others.

Secondly, it is also possible to match two elements with each other, and identify the link by a key. This key is very important, if there are more than one object from the output model, that should be linked with an object of the input model.

As a consequence, the Context API is as follows:

  • void put(EObject source, String key, EObject value) to match the produced value with the source object, discriminated by the key value
  • EObject get(EObject source, String key) to get the output element linked with the source and identified by the key
  • Collection getRoots() to get all the roots registered within this context
  • EObject getRoot(String key) to get a specific root identified by the provided key
  • void put(EObject root, String key) to add a new root to this context, identified by the provided key
  • void dispose() to clear the context, once it is not used anymore. Invoking this method once the process is finished is highly recommended.

Finally, as the Context description is an Interface (ITransformationContext), you are free to override the behavior to propose your own structure. Of course, a default implementation is proposed with the framework (DefaultTransformationContext). `