Jump to: navigation, search

Texo/Runtime Model

Introduction

Runtime model access is very useful when trying to implement generic functions like export/import, security, archiving etc. Also when the model is explicitly used in the application then it can make sense to access model objects (entities) in a model-driven way.

Using the model at runtime, to access objects and to implement generic functions makes sense because the model most of the time describes all the important domain concepts. Instead of using the model one can also access objects using the java reflective api (possibly with some filtering using annotations). However, the java api offers a technical interface which does not necessarily corresponds with the domain concepts of the model.

Note to make use of runtime model access make sure that you 'touch' the generated ModelPackages before using the runtime Texo layer, see this tip for more information.

Dependencies

For using the model-at-runtime approach there are 3 additional dependencies which are needed as of Texo 0.1.0:

  • org.eclipse.emf.common
  • org.eclipse.emf.ecore
  • org.eclipse.emf.ecore.xmi

See also Using Texo at runtime outside of Eclipse/OSGi.

Model Classes

To support a model-driven approach at runtime, Texo generates two extra java class files for each EPackage. These can be normally be found in the same package as the generated entities:

  • ModelPackage: provides access to the EPackage and its content (EClasses, EDataTypes, etc.)
  • ModelFactory: contains factory methods to create instances of an EClassifier and for String conversion. The generated String conversion methods can be implemented manually, they are used by XML/XMI de-serialization. Another important characteristic is that this class contains the generated model wrappers, i.e. the ModelObjects.

The ModelObject wraps a generated pojo and gives it a 'model face'.

Different Approaches

Note: There are several alternatives to generating model wrappers (for example generate annotations in the java code and use reflection). The approach with generated ModelObject wrappers has been chosen because:

  • it performs much better than reflective access (about 100 times faster)
  • it does not require runtime class enhancements, i.e. no complex class loader configurations which can interfere with other frameworks.
  • the pojo's do not have runtime dependency on Texo annotations.

The only 'price' is an extra class (the ModelFactory) which contains an inner-class for each EClass.

Note: the generation of the runtime model layer is completely optional and can be disabled through setting an annotation in the model, see here for more information.

Model Wrappers: the ModelObject

As noted in the previous section, the ModelFactory class contains so-called ModelWrappers. The model wrappers are the key to providing model-level access through a pojo, i.e. they wrap a pojo and give the pojo a 'model-face'.

To illustrate this with the EMF library example. For the EMF Library EClass, the following is generated:

  • a Library java class (a true pojo)
  • a LibraryModelObject which can be found inside the generated LibraryModelFactory class

The Library java class will have methods like getName, getBooks, etc. The LibraryModelObject provides the generic 'model-face', it has three methods:

  • eClass(): returning the Library EClass
  • eGet(EStructuralFeature): to get the value of an EStructuralFeature
  • eSet(EStructuralFeature, Object): to set the value of an EStructuralFeature

These three methods are also present in the other ModelObjects generated for the EClasses of the EPackage. The next section will show how these methods can be used for model access to a pojo.

Model-driven access to Pojo's

The ModelObject (eClass, eGet, eSet) interface makes it possible to access generated pojo's at generic model level. Then how do you create or wrap a generated pojo into a ModelObject? This is where the org.eclipse.emf.texo.model.ModelResolver can be used. The ModelResolver provides global access to all registered/initialized ModelPackages. It also makes it easy to access pojo's in a 'model-driven' way. The ModelResolver.getModelObject(Object) is used for this.

Let's show an example. This example creates a pojo (Book), sets some values and then accesses this same pojo in a model-driven way:

final LibraryModelFactory factory = LibraryModelPackage.MODELFACTORY;
// create the pojo and set some values
final Book book = new Book();
book.setTitle(TITLE);
book.setCategory(BOOK_CATEGORY);
book.setPages(PAGES);
 
// now access this same pojo at model-level
final ModelObject<?> modelObject = ModelResolver.getInstance().getModelObject(book);
for (EStructuralFeature eFeature : modelObject.eClass().getEAllStructuralFeatures()) {
    System.err.println(eFeature.getName() + ": " + modelObject.eGet(eFeature)); //$NON-NLS-1$
}

The for loop walks through all the EStructuralFeatures of the EClass. The following is printed:

title: title
pages: 27
category: ScienceFiction
author: null

The example above created a Book instance and used it directly. Let's show how model access can be used to create a generic method which can convert any list of objects to xml (as it is a simple example it ignores things like conversions etc. and only handle EAttributes).

    // walk over the objects
    for (Object object : objects) {
      // wrap the object
      final ModelObject<?> modelObject = ModelResolver.getInstance().getModelObject(object);
 
      sb.append("<" + modelObject.eClass().getName() + ">\n"); //$NON-NLS-1$ //$NON-NLS-2$
 
      // iterate over the EAttributes
      for (EAttribute eAttribute : modelObject.eClass().getEAllAttributes()) {
        // create the XML
        sb.append("<" + eAttribute.getName() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
 
        // convert to a string
        final Object value = modelObject.eGet(eAttribute);
        final String strValue = ModelResolver.getInstance().convertToString(
            eAttribute.getEAttributeType(), value);
        sb.append(strValue);
 
        sb.append("</" + eAttribute.getName() + ">\n"); //$NON-NLS-1$ //$NON-NLS-2$
      }
      // and close the tag
      sb.append("</" + modelObject.eClass().getName() + ">\n"); //$NON-NLS-1$ //$NON-NLS-2$
    }
    sb.append("</Root>\n"); //$NON-NLS-1$
    return sb.toString();
  }

This snippet walks through the objects, wraps each of them in a ModelObject, gets the EClass and iterates over the EAttributes. Note also that it uses the ModelResolver to perform String conversion! The ModelResolver provides several methods which are useful when implementing model-level access.

The output when passing a list with a Library, Book and Writer will be something like this:

<Root>
<Library>
<name>library</name>
</Library>
<Writer>
<name>writer</name>
</Writer>
<Book>
<title>title</title>
<pages>27</pages>
<category>ScienceFiction</category>
</Book>
</Root>

Access to the ModelFactory and ModelPackage

There are several ways to get to the ModelFactory or ModelPackage instance. The first method is to use the generated statics:

final LibraryModelFactory modelFactory = LibraryModelPackage.MODELFACTORY;
final LibraryModelPackage modelPackage = LibraryModelPackage.INSTANCE;

Then the model package instance also has a getModelFactory method:

public LibraryModelFactory getModelFactory() {..}

Another method is to use the org.eclipse.emf.texo.model.ModelResolver. The ModelResolver roughly has the same meaning as the EMF EPackageRegistry. All initialized ModelPackage instances are present in the global ModelResolver instance (which can be retrieved using ModelResolver.getInstance()). The following code snippet for example returns the LibraryModelPackage:

ModelPackage modelPackage = ModelResolver.getInstance().getModelPackage(LibraryModelPackage.NS_URI);

ModelResolver: convenient model access

The ModelResolver offers several methods which can be useful when doing model-level access at runtime:

  • getModelPackage(nsURI): find a ModelPackage using the name space uri
  • isModelEnabled(Object): to check/validate that the passed object is indeed a model object
  • getModelObject(Object): wrap a object into a ModelObject
  • convertToString(EDataType, Object): convert a value typed with an EDataType to a String
  • createFromString(EDataType, String): create a value from a String.