M2T-JET-FAQ/How do I navigate an XMI model with JET?

From Eclipsepedia

Jump to: navigation, search

Contents

Question

How do I navigate an XMI model with JET? My XMI Model is

<?xml version="1.0" encoding="ASCII"?>
<OrderSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="www.acceleo.org/myDemo" xsi:schemaLocation="www.acceleo.org/myDemo
myDemo.ecore">
<orders id="XYZ001" customer="//@customers.0" articles="//@articles.0
//@articles.2 //@articles.3"/>
<orders id="XYZ002" customer="//@customers.2" articles="//@articles.2
//@articles.3"/>
<orders id="XYZ003" customer="//@customers.1" articles="//@articles.0
//@articles.3"/>
<orders id="XYZ004" customer="//@customers.0" articles="//@articles.2
//@articles.3 //@articles.1"/>
<customers name="customer 1" address="address of customer 1"/>
<customers name="customer 2" address="address of customer 2"/>
<customers name="customer 3" address="address of customer 3"/>
<articles description="article 1" price="25.0"/>
<articles description="article 2" price="12.5"/>
<articles description="article 3" price="35.0"/>
<articles description="article 4" price="49.95"/>
</OrderSystem>

My Ecore model defines the following types:

class OrderSystem {
   containment reference orders[0..*] : Order
   containment reference customers[0..*] : Customer
   containment reference articles[0..*] : Article
}

class Order {
   attribute id: EString
   reference customer[1] : Customer
   reference articles[0..*] : Article
}

class Customer {
   attribute name : EString
   attribute address: EString
}

class Article {
   attribute description : EString
   attribute price : EFloat
}

Note: This is a pseudo language meant to emphasize EMF reference and attribute features of the model

Answer

Surprising as it may be, JET does not know how to read XMI, or XML, or any other file format! Instead, a JET transformation delegates to a model loader whose job it is to read information on the disk (such as the above XMI file), and return an in-memory representation of the data for JET to use.

The surprises do not stop there, though. JET does not know how to directly interpret the the objects returned by a 'model loader'! To understand this objects, JET delegates to objects called XPath inspectors whose job it is to put an XPath data model facade around these objects. That is, an 'inspector' can make any object look like it is a Node in a XML document.

Out-of-the-box, JET includes the following model loaders:

Id Default for Description
org.eclipse.jet.emfxml *.xml files Loads XML documents via EMF's XML parsing capabilities. Will also load EMF serialized documents. For XML documents, returns an EMF EObject subclass instance representing the document root. For other EMF models, returns an EMF Resource object.
org.eclipse.jet.emf file extensions registered with EMF Loads EMF serialized documents. Return an EMF Resource object.
org.eclipse.jet.xml none Loads XML documents via the org.w3c.dom. Returns an org.w3c.dom.Document.
org.eclipse.jet.resource none Loads an Eclipse workspace resource (file, folder, project). Returns an org.eclipse.core.resources.IResource.
org.eclipse.jet.java *.java files Loads a Java source file via the Java Development Tools (JDT). Returns a wrapper on org.eclipse.jdt.core.dom.ASTNode representing the document. (All the children are vanilla ASTNode objects.)

Loading XMI files

OK, but how does this answer the question? Here's what JET tries to do when it encounters a file:

  1. Use the model loader explicitly supplied in the transformation's plugin.xml. (By default, there isn't one.)
  2. See if there is a model loader registered for the file's extension.
  3. As a last resource, use the org.eclipse.jet.emfxml loader.

So, if your XMI file's extension is registered with EMF (via the org.eclipse.emf.ecore.extension_parser extension point), JET will automatically load the XMI document just as if you had used the EMF APIs to load the model. In this case, JET will be working directly with EMF EObjects (with the help of an inspector that interprets them for JET).

If your XMI file's extension is not registered, but you have generated the EMF generated Java code implementing your model, and installed that plug-in into your workspace, the EMFXML model loader will recognize the namespace URI in the root element, and do the same thing as above.

If neither of the above are true (perhaps because your still defining the model in the workspace), you will likely get a very confusing error message such as "Error: Feature 'version' not found." This is the result of total confusion on the part of EMF. But, you can get JET your XMI anyhow but including an xsi:schemaLocation attribute on your model. The easiest way is to use the EMF reflective editor. You may also need to explicitly set the model loader to org.eclipse.jet.emf.

The bottom line: with an EMF-generated XMI file, JET will load the EObjects represented by the XMI.

XPath expressions on EObjects

Now that your XMI is loaded as a network of EObjects, the question becomes: How do I write XPath expressions against EObjects?

JET provides a number of inspectors that understand EObjects and the various related types. They all use EMF's reflective API's to reveal the EMF features (attributes and references). The inspectors interpret EMF attributes as XML attributes. The inspectors interpret EMF references as XML child elements. The following rules apply:

  • / - the object returned by the model loader. This is the document root, and corresponds to an EMF Resource object.
  • /contents - The contents of a EMF Resource object. Equivalent to Resource.getContents().
  • /* - an alternative to /contents
  • $someObject/@attr - Retrieve the value of the attr EMF attribute on the EObject referenced by variable $someObject. Equivalent to calling the getAttr() method.
  • $someObject/ref = Retrieve the objects in the ref EMF reference on the EObject referenced by variable $someObject. Equivalent to calling getRef().
  • $someObject/Type = Retrieve contained objects whose EClass is Type. Equivalent to calling eContents() and then filtering for objects whose eClass() is Type.

Some example XPath expressions:

Retrieve the top object in the example model. (All are equivalent)

/contents
/*
/OrderSystem

Return all the orders in the system (assuming $os refers to an OrderSystem object):

$os/orders

Return the customer making an order (assuming $order references an order). Try to figure how to do this if you were interpreting the XMI directly.

$order/customer

Assuming $os refers to an OrderSytem object, retrieve the orders for a customer named 'Henry'

$os/orders[customer/@name = 'Henry']

Put it all together with some JET tags - list all the orders in the system, by customer.

<c:setVariable var="os" select="/*"/>

<c:setVariable var="lastCustomerName" select="  ''  "/>
<c:iterate select="sort($os/orders, 'customer/@name')" var="order">
  <c:if test="not($lastCustomerName = $order/customer/@name)">
    Orders for customer: <c:get select="$order/customer/@name"/>
    <c:setVariable var="lastCustomerName" select="$order/customer/@name"/>
  </c:if>
      Order <c:get select="$order/@id"/>
  <c:iterate select="$order/articles" var="article">
        Article: <c:get select="$article/@description"/>
  </c:iterate>
</c:iterate>

This produces the following output:

    Orders for customer: customer 1
      Order XYZ001
        Article: article 1
        Article: article 3
        Article: article 4
      Order XYZ004
        Article: article 3
        Article: article 4
        Article: article 2
    Orders for customer: customer 2
      Order XYZ003
        Article: article 1
        Article: article 4
    Orders for customer: customer 3
      Order XYZ002
        Article: article 3
        Article: article 4

Finally, if you are using JET 1.0.0 or later, you can simplify the example code to the following:

<c:with select="/*">

<c:setVariable var="lastCustomerName" select="  ''  "/>
<c:iterate select="sort(orders, 'customer/@name')">
  <c:if test="not($lastCustomerName = customer/@name)">
    Orders for customer: ${customer/@name}
    <c:setVariable var="lastCustomerName" select="customer/@name"/>
  </c:if>
      Order ${@id}
  <c:iterate select="articles">
        Article: ${@description}
  </c:iterate>
</c:iterate>
</c:with>