Build metamodels with dynamic EMF

Create dynamic Ecore-based models on demand without generating Java implementation classes

Learn how the Dynamic Eclipse Modeling Framework (EMF) allows developers to build dynamic Ecore-based models on demand without the need to generate Java™ implementation classes. This article introduces the APIs, and shows how to serialize and load dynamic Ecore models and their instances.

Nidhi Singh, Software Engineer, IBM India Software Labs

Nidhi Singh Nidhi Singh is a developer in the Autonomic Computing Technology Centre at IBM India. She has authored EMF-based editors while building Self-Managed Resource (SMR) run-time components. She has been with IBM for more than three years. Her interest areas include data structures, algorithms, and discrete mathematics.



Rohit Babbar (rohitba@in.ibm.com), Software Engineer, IBM India Software Labs

Rohit BabbarRohit Babbar is working as a software engineer as part of IBM's Host Access Client Package team under Rational software group. His primary interests include topics in theoretical computer science like graph theory and approximation algorithms.



20 November 2007

Also available in Russian Japanese

Eclipse Modeling Framework (EMF) describes data models and enables easy code generation from different types of data model artifacts, such as XML Schema, the Rational Rose® model, the Ecore model, or Java annotations. In the process of code generation, the EMF generator creates model code, which includes type-safe interfaces and implementation classes for the data model. However, in certain situations, these type-safe interfaces and implementation classes are not required by the application. Instead, data objects are required that could be shared among or processed further by application components.

In such situations, Dynamic EMF comes in handy because it allows application developers to manufacture an in-memory core model at run time programmatically, make instances of it dynamically, and access the model instance elements using the EMF reflective API.

Why Dynamic EMF?

The primary value of Dynamic EMF is that it allows you to build an Ecore-based model at run time with just few lines of code, then create and access instances of this dynamic model for a variety of purposes. Building a core model in this way helps avoid generating the interfaces and implementation classes when they are not required.

This methodology of creating models and model instances is particularly useful in, though not limited to, the scenarios where:

  • No type-safe interfaces or implementation classes are needed— Only simple data objects are required to be shared among application components. In this case, generating model code using the EMF code generator would be overhead for the application because it would have to unnecessarily maintain and deploy the entire set of generated interfaces/classes. Using Dynamic EMF, a core model containing dynamic classes can be programmatically created and instantiated on the fly. The instances of these dynamic classes can then be used for sharing the data or for further processing by the application components.
  • Data model is known at run time only— In this scenario, since the model of the data is not known at development time, creating static models through the EMF code generator would not make a good option. Dynamic core models, which could be built and instantiated at run time, would be better suited to the application requirements in such cases.

Creating a dynamic in-memory core model

We start by building a dynamic Ecore-based model programmatically, then create dynamic instances of the model. Later, we will see how to read and write the values of elements present in the model instance.

Creating dynamic Ecore-based model/metamodel

We will consider a bookstore model to demonstrate the creation of our dynamic Ecore model. For clarity, we represent the model using the Unified Modeling Language (UML).

Figure 1. BookStore model
BookStore model

We start by creating a set of core model elements, including an EcoreFactory instance, an EcorePackage instance, two EClass instances, and an EPackage instance. See Listing 1.

Listing 1. Create core model elements
/*
* Instantiate EcoreFactory
*/
EcoreFactory theCoreFactory = EcoreFactory.eINSTANCE;

/*
* Create EClass instance to model BookStore class
*/
EClass bookStoreEClass = theCoreFactory.createEClass();
bookStoreEClass.setName("BookStore");

/*
* Create EClass instance to model Book class
*/
EClass bookEClass = theCoreFactory.createEClass();
bookEClass.setName("Book");

/*
* Instantiate EPackage and provide unique URI
* to identify this package
*/
EPackage bookStoreEPackage = theCoreFactory.createEPackage();
bookStoreEPackage.setName("BookStorePackage");
bookStoreEPackage.setNsPrefix("bookStore");
bookStoreEPackage.setNsURI("http:///com.ibm.dynamic.example.bookstore.ecore");

The EcoreFactory provides methods to create model elements like EClass, EAttribute, EPackage, etc. Using the instance of EcoreFactory, we create two EClass instances: one to represent the BookStore class and other to represent the Book class (as specified in the BookStore model). Next, we create an EPackage, where our BookStore and Book classes would ultimately be placed. Then we define the name and nsPrefix attributes of bookStoreEPackage. Since a package's name need not be unique, a URI should be provided to bookStoreEPackage to uniquely identify it. This is done by setting the value of the nsURI attribute using the setNsURI() method.

Now we create attributes, as specified by the BookStore data model, for our dynamic classes. To set modeled data types for each attribute, we instantiate EcorePackage, which contains accessors for the metaobjects to represent each data type. See Listing 2.

Listing 2. Create attributes of dynamic classes
/*
* Instantiate EcorePackage
*/
EcorePackage theCorePackage = EcorePackage.eINSTANCE;

/*
* Create attributes for BookStore class as specified in the model
*/
EAttribute bookStoreOwner = theCoreFactory.createEAttribute();
bookStoreOwner.setName("owner");
bookStoreOwner.setEType(theCorePackage.getEString());
EAttribute bookStoreLocation = theCoreFactory.createEAttribute();
bookStoreLocation.setName("location");
bookStoreLocation.setEType(theCorePackage.getEString());
EReference bookStore_Books = theCoreFactory.createEReference();
bookStore_Books.setName("books");
bookStore_Books.setEType(bookEClass);
bookStore_Books.setUpperBound(EStructuralFeature.UNBOUNDED_MULTIPLICITY);
bookStore_Books.setContainment(true);

/*
* Create attributes for Book class as defined in the model
*/
EAttribute bookName = theCoreFactory.createEAttribute();
bookName.setName("name");
bookName.setEType(theCorePackage.getEString());
EAttribute bookISBN = theCoreFactory.createEAttribute();
bookISBN.setName("isbn");
bookISBN.setEType(theCorePackage.getEInt());

Next, we need to add the attributes of each class to the eStructuralFeatures list of the respective class. And finally, we will place both of our classes in our dynamic package, bookStoreEPackage. This completes the creation of our metamodel for the given BookStore model.

Listing 3. Associate attributes with respective classes and place classes in dynamic package
/*
* Add owner, location and books attributes/references
* to BookStore class
*/
bookStoreEClass.getEStructuralFeatures().add(bookStoreOwner);
bookStoreEClass.getEStructuralFeatures().add(bookStoreLocation);
bookStoreEClass.getEStructuralFeatures().add(bookStore_Books);

/*
* Add name and isbn attributes to Book class
*/
bookEClass.getEStructuralFeatures().add(bookName);
bookEClass.getEStructuralFeatures().add(bookISBN);

/*
* Place BookStore and Book classes in bookStoreEPackage
*/
bookStoreEPackage.getEClassifiers().add(bookStoreEClass);
bookStoreEPackage.getEClassifiers().add(bookEClass);

Creating dynamic model instances

Having created our in-memory Ecore model, we can create instances of dynamic classes present in the model. We first obtain an EFactory instance by invoking the getEFactoryInstance() method on bookStoreEPackage.

Listing 4. Create dynamic instances
/*
* Obtain EFactory instance from BookStoreEPackage 
*/
EFactory bookFactoryInstance = bookStoreEPackage.getEFactoryInstance();

/*
* Create dynamic instance of BookStoreEClass and BookEClass
*/
EObject bookObject = bookFactoryInstance.create(bookEClass);
EObject bookStoreObject = bookFactoryInstance.create(bookStoreEClass);

Had the implementation of this model been generated by the EMF code generator, it would have provided implementation classes for the package and the factory of the model. The generated package being initialized (via its constructor) registers the generated factory and instantiates an eFactoryInstance reference to the generated factory class. Hence, invoking the getEFactoryInstance() method on the generated package would return the corresponding generated factory. Since we do not have any generated factory in our dynamic core model, or generated classes of any type, calling the getEFactoryInstance() method on bookStoreEPackage returns an instance of dynamic factory EFactoryImpl.

The EFactoryImpl defines the create() operation, which creates and returns a new instance of the model class specified as an argument. In the case of generated model code, this method is overridden by the generated factory to create and return instances of corresponding generated model classes. In the dynamic metamodel, since there are no generated factory/model implementation classes, invoking the create() method on the EFactory instance would return an instance of EObjectImpl.

Reading and writing the dynamic model

The EObjectImpl class contains implementation for all reflective APIs, which can be used to access attributes of our dynamic classes, enabling you to read and write the model, as shown below.

Listing 5. Get/Set the values of model instance elements
/*
* Set the values of bookStoreObject attributes
*/
bookStoreObject.eSet(bookStoreOwner, "David Brown");
bookStoreObject.eSet(bookStoreLocation, "Street#12, Top Town, NY");
((List) bookStoreObject.eGet(bookStore_Books)).add(bookObject);

/*
* Set the values of bookObject attributes
*/
bookObject.eSet(bookName, "Harry Potter and the Deathly Hallows");
bookObject.eSet(bookISBN, 157221);

/*
* Read/Get the values of bookStoreObject attributes
*/
String strOwner =(String)bookStoreObject.eGet(bookStoreOwner);
String strLocation =(String)bookStoreObject.eGet(bookStoreLocation);

/*
* Read/Get the values of bookObject attributes
*/
String strName =(String)bookObject.eGet(bookName);
Object iISBN = bookObject.eGet(bookISBN);

On similar lines, other reflective APIs implemented in EObjectImpl class (eIsSet() and eUnSet()) can be invoked on the model classes when required.


Serializing the dynamic model

The dynamic model can be serialized using four fundamental interfaces of the EMF Persistence framework: Resource, ResourceSet, Resource.Factory, and URIConverter. The procedure of serialization will depend on whether we want to serialize the model in the same program in which we would deserialize it, or if we wish to serialize it in some other program independent of the program in which we would load or deserialize the model.

Do the following if serialization and deserialization of the core model is to be done in the same program; if not, move on to Listing 7. To initialize the process of serialization, we first register XML resource factory to handle files with any extension, as shown in Listing 6. Next, we create an empty resource in the resource set by invoking the createResource() method on the ResourceSet instance and passing the actual location of the resource as URI. We add our EObject (bookStoreEObject) to the contents list of this resource and using the save() method, copy the resource to its persistence storage. (URIConverter can be used, if needed, by the resource set to locate the resource or to normalize an input URI into an actual URI of the resource.)

Listing 6. Serialize the dynamic model instance
ResourceSet resourceSet = new ResourceSetImpl();
/*
* Register XML Factory implementation using DEFAULT_EXTENSION
*/
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

/*
* Create empty resource with the given URI
*/
Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));

/*
* Add bookStoreObject to contents list of the resource 
*/
resource.getContents().add(bookStoreObject);

try{
    /*
    * Save the resource
    */
      resource.save(null);
   }catch (IOException e) {
      e.printStackTrace();
   }

The resultant serialized instance of our dynamic model would look like Figure 2.

Figure 2. Serialized instance of dynamic mode
Serialized instance of dynamic mode

Use the following serialization procedure if serialization and deserialization of the core model is to be done in different, independent programs; if not, go back to Listing 6. While loading the dynamic model, we need access to the dynamic package bookStoreEPackage. If the model is to be loaded in the same program in which it is serialized, this can be easily done (see the next section). But if the model is to be loaded in a program different from the one in which it is serialized, we need to serialize our dynamic Ecore model before serializing the model instance to be able to access bookStoreEPackage.

Listing 7. Serialize the metamodel
ResourceSet metaResourceSet = new ResourceSetImpl();

/*
* Register XML Factory implementation to handle .ecore files
*/
metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "ecore", new  XMLResourceFactoryImpl());

/*
* Create empty resource with the given URI
*/
Resource metaResource = \
metaResourceSet.createResource(URI.createURI("./bookStore.ecore"));

/*
* Add bookStoreEPackage to contents list of the resource 
*/
metaResource.getContents().add(bookStoreEPackage);

try {
     /*
     * Save the resource
     */
     metaResource.save(null);
    } catch (IOException e) {
      e.printStackTrace();
   }

We first register an XML resource factory implementation to handle files with an Ecore extension because the core model would be serialized using this extension. Next, we create an empty resource and add our dynamic package, bookStoreEPackage, to the contents list of this newly created resource. Finally, we save this resource.

The resulting serialized dynamic core model, or the metamodel, appears as shown in Figure 3.

Figure 3. Serialized dynamic core model
Serialized dynamic core model

Now we serialize the model instance document: bookStore.xml. The only difference is that this time, the instance document is serialized with an additional attribute: xsi:schemaLocation. The loader will use this attribute to locate the persisted resource, bookStore.ecore, containing the serialized EPackage needed to load the model instance document.

Listing 8. Serialize model instance document with xsi:schemaLocation attribute
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));
resource.getContents().add(bookStoreObject);

/*
* Save the resource using OPTION_SCHEMA_LOCATION save option toproduce 
* xsi:schemaLocation attribute in the document
*/
Map options = new HashMap();
options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
try{
     resource.save(options);
   }catch (IOException e) {
     e.printStackTrace();
   }

The serialized model instance document, bookstore.xml, would appear with the xsi:schemaLocation attribute, as shown below.

Figure 4. Serialized model instance with xsi:SchemaLocation attribute
Serialized model instance with xsi:SchemaLocation attribute

Deserializing/Loading dynamic model

We will now see how to load the dynamic model instance document that we just serialized.

Use this deserialization procedure if serialization and deserialization of the core model is done in the same program. As in the process of serialization, we first register XML the resource factory implementation to handle files with any extension. Thereafter, we add our dynamic bookStoreEPackage to the package registry using the same namespace URI we had given while serializing the model. (This URI appeared as xmlns:book=http:///com.ibm.dynamic.example.bookstore.ecore in our resultant serialized model instance document.)

Listing 9. Load the model instance document
ResourceSet load_resourceSet = new ResourceSetImpl();

/*
* Register XML Factory implementation using DEFAULT_EXTENSION
*/
load_resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new XMLResourceFactoryImpl());

/*
* Add bookStoreEPackage to package registry
*/
load_resourceSet.getPackageRegistry().put(
    "http:///com.ibm.dynamic.example.bookstore.ecore",
     bookStoreEPackage);

/*
* Load the resource using the URI
*/
Resource load_resource = load_resourceSet.getResource(
    URI.createURI("./bookStore.xml"),true);

/*
* Read back the serialized instances of dynamic classes stored in the 
* resource
*/
EObject readBookStoreObject = (EObject)
    load_resource.getContents().get(0);
EObject readBookObject = 
    (EObject)((EList)(readBookStoreObject.eGet(bookStore_Books))).get(0);

System.out.println("Owner: " + readBookStoreObject.eGet(bookStoreOwner)
    + "\nLocation: " + readBookStoreObject.eGet(bookStoreLocation)
    + "\nBook:\n name: " + readBookObject.eGet(bookName) 
    + "\t isbn: " + readBookObject.eGet(bookISBN));

After our package is registered with the package registry, we load the resource by invoking the getResource() method on the resource set instance. This will load our model instance document using the URI we passed as an argument to the getResource() method. Finally, we access the serialized instances in the document using reflective APIs, as shown above.

Follow this procedure if serialization and deserialization of the core model is done in different, independent programs. Here, the procedure of loading the instance document remains the same, except that we do not have to add our dynamic bookStoreEPackage to the package registry of the ResourceSet. This is because when we load the instance document, bookStore.xml, the loader would find the namespace URI to package URI mapping in the xsi:schemaLocation attribute of the instance document. Using this mapping, the loader would automatically load the serialized bookStore.ecore model containing the dynamic bookStoreEPackage. Once this dynamic package is loaded, the serialized instances can be accessed in the usual manner using EMF-reflective APIs, as illustrated in Listing 9.


Limitations

Models built using Dynamic EMF are a bit slower and use more space compared to the models generated through the EMF generator. This is because dynamic models rely on dynamic implementation of the reflective API, provided by the EObjectImpl class, for getting and setting of dynamic features of an instance. For example, the dynamic model would use a slower eGet(EstructuralFeature myFeature) method as opposed to a faster (generated) getMyFeature() method used by the generated core model. Furthermore, dynamic settings require additional space in dynamic model instances, which is not needed if the model code is generated using the EMF code generator.


Conclusion

You learned how to build Ecore-based models using Dynamic EMF that can be created and instantiated on the fly without having corresponding Java implementation classes. The usage of this approach of building models becomes particularly interesting in scenarios where part of the application model code is generated through the EMF code generator, and the rest of the model code is built using Dynamic EMF. In this and similar scenarios, Dynamic EMF, if leveraged effectively, can go a long way in enabling applications to share data reflectively. At the same time, it can reduce the generated implementation code that would otherwise need to be maintained as the model evolves.


Download

DescriptionNameSize
Sample codeos-dynamicemf.sampleDynamicBookstoreModel.zip8KB

Resources

Learn

Get products and technologies

Discuss

  • EMF newsgroups are the most useful resource for EMF questions. You can post comments to clarify any queries and keep updated about new developments.
  • The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
  • The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
  • Participate in developerWorks blogs and get involved in the developerWorks community.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=269848
ArticleTitle=Build metamodels with dynamic EMF
publish-date=11202007