Pages

Monday, November 14, 2011

Using EMF in standalone Java application


During the last days I was working with Eclipse Modeling Framework (EMF) within the context of an R&D project. As our architecture became distributed, I had to use EMF outside an Eclipse-based application. Therefore, this article deals with us of EMF in a standalone Java application (standalone meaning here not running in en Eclipse framework like server-side components, Swing applications, etc). I will not present here how to use EMF to create models and generate the respective Java code (you may find a good tutorial on that at http://www.vogella.de/articles/EclipseEMF/article.html), but I will focus on using the generated code in a standalone Java application.

Let us remind that EMF project is a modeling framework and code generation facility for building tools and other applications based on a structured data model. Models can be specified using annotated Java, XML documents, or modeling tools and then can be used to produce a set of Java classes for the model, along with a set of adapter, factories and utility classes. You may find information and  documentation about the EMF project at http://www.eclipse.org/modeling/emf/docs/ .

Naturally, EMF (and its subprojects: EMF Core, CDO, Compare, Teneo, Model Query, etc) fits well in an Eclipse environment (Eclipse IDE or RCP). Nevertheless, EMF’s runtime features (use of generated code, notification, change recording, validation, persistence, etc) can be used in a standalone Java application. In order to benefits from EMF in such application, you must add all the needed jars to your classpath. In the book “EMF Eclipse Modelling Framework” you may find a table that clearly identifies the required jars based on the desired feature:

  • Core runtime, reflective API, validation: org.eclipse.emf.common_<version>.jar and org.eclipse.emf.ecore_<version>.jar
  • XML/XMI persistence: org.eclipse.emf.ecore.xmi_<version>.jar and the previous core runtime jars
  • EMF.Edit: org.eclipse.emf.edit_<version>.jar and the core runtime jars
  • Change model: org.eclipse.emf.change_<version>.jar and the core runtime jars
  • Ecore to XML persistence mapping: org.eclipse.emf.mapping.exore2xml_<version>.jar and the core runtime and XML/XMI jars
  • XML schema Infoset Model: org.eclipse.xsd_<version>.jar and the core runtime jars.


When using EMF under Eclipse, the plug-in extension mechanism is used to perform registration at runtime. In a standalone environment, default resource factories are not registered. Therefore, for each file extension or scheme the application wants to load or save, one needs to register the corresponding resource factory. For example, to load and save XML documents, it is necessary to add a similar line in your the application:
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xml", new XMLResourceFactoryImpl());

In addition, one also needs to register one’s package, which happens as a side effect of accessing XyzPackage.eINSTANCE attribute. For example, to register the example model, it is necessary to add the following lines to the program:
MyEMFExamplePackage.eINSTANCE.eClass();
   ChangePackage.eINSTANCE.eClass(); // only if using EMF change feature

In my application I choose to code a class ResourceManager (which additionally is a Singleton) and put all these registrations in the initialization part of this object.
public class ResourceManager {

 /**
  * Create the resource manager.
  */
 private ResourceManager() {
  
  resourceSet = createResourceSet();

  // register factories
  Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xml", new XMLResourceFactoryImpl());

  // register packages
  MyEMFExamplePackage.eINSTANCE.eClass();
  ChangePackage.eINSTANCE.eClass(); // only if using EMF change feature
 }

 /**
  * Create the resource set.
  */
 private ResourceSet createResourceSet() {
  ResourceSet rs = new ResourceSetImpl();
  // Register the default resource factory -- only needed for stand-alone
  rs.getResourceFactoryRegistry()
    .getExtensionToFactoryMap()
    .put(Resource.Factory.Registry.DEFAULT_EXTENSION,
      new XMIResourceFactoryImpl());
  rs.setURIConverter(new MapItURIConverter());

  return rs;
 }
 
 // Other methods ...
}

Now that we have registered all needed factories and packages, we can use the code generated by EMF in order to manipulate, save and load objects. In this example I instantiate a “dummy” model and, save it in an xml file. Subsequently,  I load the model and record the modification in order to apply it later.

In this entry, we have seen an example of using EMF outside Eclipse, i.e., in a standalone Java application which could be a server component, a Swing GUI etc. It is to note that in order to use EMF feature in such environment, it is necessary to perform two important steps:

  • Add required jars in the classpath
  • Register Factories and packages prior to model utilization.


8 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Would it be possible to share the little example Eclipse Project you used for running EMF standalone?

    This would be very helpful for my current project.

    ReplyDelete
    Replies
    1. Hi Rouven,
      I uploaded an example on my github: https://github.com/fracaru/emf-labs/.
      The metamodel I used is in the "model" folder, you need to generate the code. Let me know if you have any problem reproducing it.
      Regards, Florin

      Delete
  3. Hi, can you please explain a little more how you manage the runtime dependencies.
    I still have ClassDefNotFound and cannot get out of it ...

    ReplyDelete
  4. For me the genmodel was missing. I created the genmodel in a separate emf project with the CollaborativeMDEProject.ecore then I copied genmodel into the org.exmaple.emf.standalone\model directory and corrected the name of the model directory in the genmodel. Now I could generate the model right into the org.exmaple.emf.standalone project.
    After exporting it into a runnable jar it works without any eclipse assistance.

    The list of the 4 referenced emf jars was in the pom file.

    Here is a man class, it contains the same model as the test. Now the process.xmi is generated in the myXMI directory nex to the jar.


    public class EMFMain {

    public static void main(String[] args) throws IOException {
    EMFMain emfMain = new EMFMain();
    JOptionPane.showMessageDialog(null, "Start ceating and saving the EMF model");
    emfMain.createAndSave();
    JOptionPane.showMessageDialog(null, "Start loading the saved EMF model");
    String result = emfMain.load();
    JOptionPane.showMessageDialog(null, "Name of the loaded project: " + result);
    }

    public void createAndSave() throws IOException {
    Registry reg = Resource.Factory.Registry.INSTANCE;
    Map extFactMap = reg.getExtensionToFactoryMap();
    XMIResourceFactoryImpl xmiResourceFactory = new XMIResourceFactoryImpl();
    extFactMap.put("xmi", xmiResourceFactory);

    CollaborativeMDEProcessPackage.eINSTANCE.eClass();

    // 2. Create the Resource
    URI uri = URI.createURI("myXMI/process.xmi");
    // 2. obtain a new ResourceSet
    XMIResource resource = (XMIResource) xmiResourceFactory.createResource(uri);
    EObject process = createModel();
    resource.getContents().add(process);

    // 5. Set the encoding option
    Map options = new HashMap<>();
    options.put(XMLResource.OPTION_ENCODING, "UTF-8");
    try {
    // 6. Save the model
    resource.save(options);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public String load() throws IOException {
    // 1. Register XMI resource factory to .bowling extension
    Registry reg = Resource.Factory.Registry.INSTANCE;
    Map map = reg.getExtensionToFactoryMap();
    XMIResourceFactoryImpl xmiResourceFactory = new XMIResourceFactoryImpl();
    map.put("xmi", xmiResourceFactory);

    // 2. Get the resource
    XMIResource resource = (XMIResource) xmiResourceFactory.createResource(URI.createURI("myXMI/process.xmi"));

    String result = "???";
    try {
    // 5. Set the encoding option
    Map options = new HashMap<>();
    options.put(XMLResource.OPTION_ENCODING, "UTF-8");
    // 6. Load the model
    resource.load(options);
    // 7. Get the model from resource
    EObject eObject = resource.getContents().get(0);

    MDEProcess process = (MDEProcess) eObject;
    result = process.getProject().getName();

    } catch (IOException e) {
    }
    return result;
    }

    private EObject createModel() {
    MDEProcess process = CollaborativeMDEProcessFactory.eINSTANCE.createMDEProcess();

    Project project = CollaborativeMDEProcessFactory.eINSTANCE.createProject();
    project.setId(1);
    project.setName("MyProject");
    project.setDescription("Super project");

    Participant john = CollaborativeMDEProcessFactory.eINSTANCE.createParticipant();
    john.setId(1);
    john.setEmail("john.doe@hotmale.com");
    john.setFirstName("John");
    john.setLastName("DOE");

    Participant jean = CollaborativeMDEProcessFactory.eINSTANCE.createParticipant();
    jean.setId(2);
    jean.setEmail("jean.DUPONT@hotmale.com");
    jean.setFirstName("Jean");
    jean.setLastName("DUPONT");

    project.getParticipants().add(jean);
    project.getParticipants().add(john);
    process.setProject(project);
    return process;
    }
    }

    ReplyDelete

/* */