he Eclipse Modeling Framework (EMF) is a Java open source framework and code-generation facility for building tools and other applications based on a structured model. While the Eclipse Platform provides a powerful integration framework at the UI and file level, EMF enhances this capability to enable fine-grained data sharing among tools and applications.
Similar to other Java binding frameworks, e.g. JAXB or XMLBeans, given a model, EMF can generate Java source code that will allow you to create, query, update, deserialize, and serialize instances of your models. While the majority of Java binding frameworks support just one class of models, for example XML Schema, EMF supports generating code from XML Schema, UML class diagrams (Rational Rose or UML2), and annotated Java interfaces. In addition to the model code, EMF can also generate a complete application that includes a customizable editor.
The EMF-generated code has a built-in change notification mechanism and supports cross-document references. EMF provides a reflective API to access instances of your models and allows you to dynamically create models. EMF supports validation of model constraints. EMF provides powerful code generation tools that support regeneration of models and merging with user written code.
In this article, we’ll explain just what the EMF is, take a look at the basic architecture.
EMF started out as an implementation of the Object Management Group’s (OMG) Meta Object Facility (MOF) specification, which standardizes a metamodel for object oriented analysis and design. Over time, EMF was used to implement a large set of tools and thus evolved into an efficient Java implementation of a core subset of the MOF API.
The MOF-like core metamodel (model of a model) in EMF is called Ecore. In the current proposal for MOF 2.0, there is a similar subset of the MOF model, called Essential MOF (EMOF), which is separated out. There are small, mostly naming differences between Ecore and EMOF and therefore EMF can transparently read and write serializations of EMOF, allowing standard interchange of data between tools.
Today EMF’s use is widespread. For example, EMF is used to implement the open source XML Schema Infoset Model (XSD), Service Data Objects (SDO), UML2, and Web Tools Platform (WTP) projects at Eclipse. In addition EMF is used in commercial products, such as Omondo EclipseUML and IBM Rational and WebSphere products.
Ecore and the Reflective API
One of the key interfaces in EMF is EObject, which is conceptually equivalent to java.lang.Object. All modeled objects, generated or not, implement this interface in order to provide several important features:
- Similarly to Java’s Object.getClass(), using the eClass() method, you can retrieve the metadata of the instance, i.e., its EClass.
- On any EMF modeled object you can use the reflective API (eGet(), eSet()) to access its data. This is conceptually equivalent to Java’s java.lang.reflect.Method.invoke() method, though much more efficient.
- From any instance object you can get its container (parent) using the eContainer() method.
- EObject also extends Notifier, which allows you to monitor all changes to the object’s data.
|Figure 1. The Ecore Class Hierarchy: The image shows the complete class hierarchy of the Ecore metadata.|
As mentioned previously, EMF has its own simple metadata called Ecore. Figure 1 shows the complete class hierarchy of the Ecore metadata. In Figure 1, you can see that EPackage contains information about model classes (EClass) and data types (EDataType). EClass represents a modeled class, and specifies the attributes and references representing the data of instances. EAttribute represents simple data, specified by an EDataType. EReference represents an association between classes; its type is an EClass. EFactory contains methods to create model elements.
To find out more about EMF and Ecore please read the online overview or purchase the Eclipse Modeling Framework (EMF). The EMF Web site has several documents describing how to generate Java code from an XML Schema or UML diagram using EMF.
The next section describes an example which uses Ecore to create a simple model for companies, and then uses dynamic EMF to create, serialize and deserialize instances of this model. If you wish to follow along and you are already an Eclipse user, download and install the EMF 2.1 SDK or any newer version available on the EMF download site. Otherwise, you can download the standalone package, which includes EMF jars that do not have any dependencies on Eclipse and can be used in a standalone application.
Using Dynamic EMF Capabilities
In general, if you have models at development time, it is typically best to generate Java code because in this case your application will use less memory and provide faster access to data (using either the generated or the reflective API). While generating Java code serves a need for most applications, this might not always be the case. You might need to process data without requiring the availability of generated implementation classes. For example, you might not know at development time the model of the data you will be processing, making generated Java code a poor option.
Dynamic, i.e., non-generated, classes can be created at runtime in several ways. Let’s start by creating a company model programmatically using the Ecore API. The company model describes a company that has a name and departments. Each department is identified by a number and has employees. Each employee has a name. The code below shows an Ecore metamodel that corresponds to this model.
EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;EcorePackage ecorePackage = EcorePackage.eINSTANCE;// create an Company classEClass companyClass = ecoreFactory.createEClass();companyClass.setName("Company");// create company nameEAttribute companyName = ecoreFactory.createEAttribute();companyName.setName("name");companyName.setEType(ecorePackage.getEString());companyClass.getEStructuralFeatures().add(companyName);//create an Employee classEClass employeeClass = ecoreFactory.createEClass();employeeClass.setName("Employee");//add a name attribute to an Employee classEAttribute employeeName = ecoreFactory.createEAttribute();employeeName.setName("name");employeeName.setEType(ecorePackage.getEString());employeeClass.getEStructuralFeatures().add(employeeName);//create a Department classEClass departmentClass = ecoreFactory.createEClass();departmentClass.setName("Department");//add department identification numberEAttribute departmentNumber = ecoreFactory.createEAttribute();departmentNumber.setName("number");departmentNumber.setEType(ecorePackage.getEInt());departmentClass.getEStructuralFeatures().add(departmentNumber);//department class can contain reference to one or many employeesEReference departmentEmployees = ecoreFactory.createEReference();departmentEmployees.setName("employees");departmentEmployees.setEType(employeeClass);// specify that it could be one or more employeesdepartmentEmployees.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);departmentEmployees.setContainment(true);departmentClass.getEStructuralFeatures().add(departmentEmployees);// company can contain reference to one or more departmentsEReference companyDepartments = ecoreFactory.createEReference();companyDepartments.setName("department");companyDepartments.setEType(departmentClass);companyDepartments.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);companyDepartments.setContainment(true);companyClass.getEStructuralFeatures().add(companyDepartments);//create a package that represents companyEPackage companyPackage = ecoreFactory.createEPackage();companyPackage.setName("company");companyPackage.setNsPrefix("company");companyPackage.setNsURI("http:///com.example.company.ecore");companyPackage.getEClassifiers().add(employeeClass);companyPackage.getEClassifiers().add(departmentClass);companyPackage.getEClassifiers().add(companyClass);
Using the reflective API you can create and initialize an instance of your model:
// get company factoryEFactory companyFactory = companyPackage.getEFactoryInstance();// using the factory create instance of company class and // set company nameEObject company = companyFactory.create(companyClass);company.eSet(companyName, "MyCompany");// create an instance of employee classEObject employee = companyFactory.create(employeeClass);//using reflective API initialize name of employeeemployee.eSet(employeeName, "John");// create an instance of department classEObject department = companyFactory.create(departmentClass);department.eSet(departmentNumber, new Integer(123));//add "John" to department((List)department.eGet(departmentEmployees)).add(employee);// add the department to the company((List)company.eGet(companyDepartments)).add(department);
Serializing and Deserializing Data
To serialize your model instances, you need to put a root object of your instance model into a resource. The EMF org.eclipse.emf.ecore.resource.Resource interface represents a physical storage location (such as file or URL) and provides methods to serialize and load data. Each resource is stored in a ResourceSet, which represents a collection of resources that have been created and loaded together, allowing for references among them. In particular, a ResourceSet keeps track of which resources have been loaded and makes sure that no resource in a ResourceSet is loaded twice.
Because EMF is capable of dealing with multiple model sources, e.g., XML Schema, it is also important to specify which resource implementation should be used for (de)serializing your data. Normally, when you invoke the ResourceSet.createResource(URI) method, it queries the Resource.Factory.Registry to look up a factory that is registered for that URI and uses it to create an appropriate resource implementation. Therefore, before you (de)serialize your data ensure that you register the appropriate resource factory implementation. EMF provides several Resource.Factory implementations:
- For XML data use org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl.
- For XMI data use org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl.
- For Ecore models use org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl.
With these EMF resources in your toolbox, you can use this code to serialize your data:
// create resource set and resource ResourceSet resourceSet = new ResourceSetImpl();// Register XML resource factoryresourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());Resource resource = resourceSet.createResource(URI.createFileURI("c:/temp/company.xmi"));// add the root object to the resourceresource.getContents().add(company);// serialize resource – you can specify also serialization// options which defined on org.eclipse.emf.ecore.xmi.XMIResourceresource.save(null);
The serialized form of the company.xmi is:
During deserialization, the namespace URIs of XML data are used to locate the required Ecore packages (which describe the model for your instance documents). Therefore, before you attempt to load any model, ensure that you register the namespace URI for each Ecore package your documents will be using:
// register package in local resource registryresourceSet.getPackageRegistry().put(companyPackage.getNsURI(), companyPackage);// load resource resource.load(null);
It is also important to notice the difference between local and global package (EPackage.Registry.INSTANCE) and resource factory (Resource.Factory.Registry.INSTANCE) registries. The global registry is static and therefore any application during lifetime of JVM can access the global registry and possibly overwrite it. To ensure that your registrations do not overwrite global registrations and vice versa, it is typically better to use local resource set registry.
Generating Dynamic Ecore from XML Schema
As mentioned previously, if your model is an XML Schema but you choose not to generate Java classes, you can dynamically create an Ecore model using the XSDEcoreBuilder. This example uses ipo.xsd:
XSDEcoreBuilder xsdEcoreBuilder = new XSDEcoreBuilder();ResourceSet resourceSet = new ResourceSetImpl();Collection eCorePackages = xsdEcoreBuilder.generate(URI.createFileURI("c:/temp/ipo.xsd"));
The generate method returns Ecore packages that are created for each URI in this schema. If the schema imports other namespaces, more than one Ecore package is returned. Each package is registered locally in the resource set used to convert schema. Therefore, if you use the same resource set to load your instance XML document, you don’t need to register the packages yourself.
Because XML Schema includes more concepts than Ecore, for example wildcards, EMF uses Ecore EAnnotations to record the mapping to XML Schema. During (de) serialization of the data EMF needs to process these annotations. To ensure these annotations are respected during (de) serialization, you must use the XMLResource.ExtendedMetaData option:
HashMap options = new HashMap();options.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);// refer http://www.w3.org/TR/2004/PER-xmlschema-0-20040318/#ipo.xmlResource resource = resourceSet.createResource(URI.createFileURI("c:/temp/ipo.xml"));resource.load(options);
EMF 2.1 also adds a new capability that allows you to convert schemas to Ecore on the fly while loading an XML document that contains an xsi:schemaLocation or xsi:noNamespaceSchemaLocation attribute. It also allows you to load an XML document that has no schema associated with it. To use this functionality you need to register org.eclipse.emf.ecore.xmi.impl.GenericXMLResourceFactoryImpl:
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml", new GenericXMLResourceFactoryImpl());
This article gave you a short introduction to EMF, explaining the core EMF concepts, and provided useful examples on how to exploit the dynamic capabilities of EMF.
|Note: The opinions expressed in this paper are those of the authors, not of the IBM Corporation. IBM, alphaWorks, developerWorks, and WebSphere are trademarks of International Business Machines Corporation in the United States, other countries, or both. Rational is a registered trademark of International Business Machines Corporation and Rational Software Corporation, in the United States, other countries or both. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Other company, product and service names may be trademarks or service marks of others.|