Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Teneo/Hibernate/XMLSchema/FeatureMap

EMF uses Feature Maps in a number of situations, for example to implement XML Schema repeating model groups or to implement derived features. The Teneo persistency layer supports Feature Maps and generates the required Hibernate mappings. Teneo FeatureMap support is hidden for the developer. The information here is mainly intended to help understand the created mappings and relational tables.

See here for a description on how EMF uses FeatureMaps to implement derived features. This page uses a similar example.

FeatureMap for derived features

Example Model

This section illustrates how a FeatureMap for a derived feature is mapped and persisted. The model is defined below using an XML Schema.

<xsd:complexType name="Supplier">
	<xsd:sequence>
		<xsd:element name="name" type="xsd:string"/>
  			<xsd:choice maxOccurs="unbounded" ecore:name="orders">
  				<xsd:element name="preferredOrders" type="PurchaseOrder"/>
  				<xsd:element name="standardOrders" type="PurchaseOrder"/>
  				<xsd:element name="hardCopyOrderReference" type="xsd:string"/>
  				<xsd:element name="hardCopyOrderNumber" type="xsd:long"/>
	  		</xsd:choice>
  	</xsd:sequence>
</xsd:complexType>

The xsd:choice with maxOccurs="unbounded" results in a FeatureMap EAttribute with name orders. In the generated java code this will result in an extra member (named orders) and accessor methods.

There are 4 derived features. The first two are references to PurchaseOrder, the last two are primitive/simple type derived features.


Generated Code by EMF

EMF will generate the relevant methods to access the derived features. Derived features will not have java members but will refer to accessor of the FeatureMap EAttribute. For example the following code shows how the getPreferredOrders uses the getOrders accessor.

public EList getPreferredOrders() { return ((FeatureMap)getOrders()).list(SimplefeaturemapPackage.eINSTANCE.getSupplier_PreferredOrders()); }


Mapping to a relational store

A FeatureMap is actually an EList with FeatureMap.Entry objects as elements. A FeatureMap.Entry has two members: 1) a StructuralFeature which defines what type of entry it is, and 2) the actual value.

For the relational mapping this same structure is used. For each FeatureMap, Teneo will create a separate class mapping. This class mapping is persisted using the org.eclipse.emf.teneo.mapping.elist.FeatureMapEntryTuplizer. The created class mapping contains a property for each of the possible types in a featuremap. This ensures that foreign key constraints are created and enforced.

The mapping for the example above is displayed here. First the mapping of the orders property in Supplier:

<list name="orders" lazy="true" cascade="all,delete-orphan">
	<key update="true">
		<column name="SUPPLIER_ORDERS_ID" not-null="false" unique="false"/>
	</key>
	<list-index column="SUPPLIER_ORDERS_IDX"/>
	<one-to-many entity-name="Supplier_orders"/>
</list>

The orders property is mapped as a list with a one-to-many with an entity with a specific name. This name is generated by Teneo and used in the class mapping of the feature map entry:

<class entity-name="Supplier_orders" lazy="false" table="SUPPLIER_ORDERS">
 
	<meta attribute="eclass">Supplier</meta>
	<id type="long">
		<generator class="native"/>
	</id>
	<version name="_vn_"/>
 
	<property name="fme_feature" type="java.lang.String"/>
 
	<many-to-one name="preferredOrders" entity-name="PurchaseOrder" cascade="all" lazy="false" 
			insert="true" update="true" not-null="false">
		<column not-null="false" unique="false" name="PURCHASEORDER_PREFERREDORDERS_ID"/>
	</many-to-one>
	<many-to-one name="standardOrders" entity-name="PurchaseOrder" cascade="all" lazy="false" 
			insert="true" update="true" not-null="false">
		<column not-null="false" unique="false" name="PURCHASEORDER_STANDARDORDERS_ID"/>
	</many-to-one>
	<property name="hardCopyOrderReference" type="java.lang.String" lazy="false" not-null="false"/>
	<property name="hardCopyOrderNumber" type="long" lazy="false" not-null="false"/>
</class>

The feature map entry mapping contains a property for each of the possible feature map entry types. In addition it contains a property (fme_feature) to store a String to identify the EFeature.

The persistency logic is hidden for the developer. A FeatureMap which is read from a persistent store will contain entries which implement the FeatureMap.Entry interface.


Using FeatureMaps

The persisted FeatureMaps can be used in exactly the same way as 'standard' Feature Maps (See here). As an example of this see the code below to create a supplier and set the orders feature.

PurchaseOrder pref1 = factory.createPurchaseOrder();
pref1.setName("preferred1");
PurchaseOrder stand1 = factory.createPurchaseOrder();
stand1.setName("standard1");
PurchaseOrder stand2 = factory.createPurchaseOrder();
stand2.setName("standard2");
 
Supplier supplier = factory.createSupplier();
supplier.setName("supplier");
supplier.getOrders().add(SimplefeaturemapPackage.eINSTANCE.getSupplier_StandardOrders(), stand1);
supplier.getOrders().add(SimplefeaturemapPackage.eINSTANCE.getSupplier_PreferredOrders(), pref1);
supplier.getOrders().add(SimplefeaturemapPackage.eINSTANCE.getSupplier_HardCopyOrderReference(), "ref1");
supplier.getOrders().add(SimplefeaturemapPackage.eINSTANCE.getSupplier_HardCopyOrderNumber(), new Long(1002));
supplier.getOrders().add(SimplefeaturemapPackage.eINSTANCE.getSupplier_StandardOrders(), stand2);
 
The source code below shows how this information can be retrieved using the derived feature accessors. As is illustrated the persistency extensions are not visible to the developer:
 
EList standardList = supplier.getStandardOrders(); // returns the list of standard orders, there are 2
EList preferredList = supplier.getPreferredOrders(); // returns the list of preferred orders, there is 1
EList numberList = supplier.getHardCopyOrderNumber();  // returns the list of hard copy numbers
EList referenceList = supplier.getHardCopyOrderReference(); // returns the list hard copy references
 
// Will display java.lang.Long
System.err.println(numberList.get(0).getClass().getName());
 
// Will display preferred1
System.err.println(((PurchaseOrder)preferredList.get(0)).getName());
 
// Retrieve directly through the main featuremap member, 
// this is the same purchaseorder as in the previous step
FeatureMap.Entry entry = (FeatureMap.Entry)supplier.getOrders().get(1);
PurchaseOrder referencePO = (PurchaseOrder)entry.getValue();
// this will also display preferred1
System.err.println(referencePO.getName());

FeatureMap for Mixed content

XML Schema allows text data to be mixed with XML nodes, this is called Mixed content. An example is the xml document below (copied from the XML Schema Primer). In this XML document text (such as 'Dear Mr.') is mixed with XML nodes (such as <name>Robert Smith</name>).

<letterBody>
Dear Mr.<name>Robert Smith</name>.
Your order of <quantity>1</quantity> <productName>Baby
Monitor</productName> shipped from our warehouse on
<shipDate>1999-05-21</shipDate>. ....
</letterBody>

The support for mixed content is described in section 3.4 in the XML Schema to Ecore Mapping. In short EMF generates a FeatureMap EAttribute with the name mixed for each complexType with mixed="true". All elements of the complexType are part of this FeatureMap.

For example the xsd below represents the xml document above. For this XML Schema, EMF will generate a java class with name LetterBody. This class has a FeatureMap member mixed. For the four elements no java members are generated but only accessor methods which use the mixed FeatureMap.

<xsd:element name="letterBody">
  <xsd:complexType mixed="true">
    <xsd:sequence>
      <xsd:element name="name" type="xsd:string"/>
      <xsd:element name="quantity" type="xsd:positiveInteger"/>
      <xsd:element name="productName" type="xsd:string"/>
      <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
      <!-- etc. -->
    </xsd:sequence>
  </xsd:complexType>
</xsd:element>

Mixed content is mapped to a relational store in the same way as the FeatureMap above. The only difference with the standard FeatureMap mapping a mixed content FeatureMap will contain three fields for storing mixed content: fme_mixed_text, fme_mixed_cdata and fme_mixed_comment:

<class entity-name="LetterBodyType_mixed" lazy="false" table="LETTERBODYTYPE_MIXED">
	<meta attribute="eclass">LetterBodyType</meta>
    <id type="long">
      <generator class="native"/>
    </id>
    <version name="_vn_"/>
 
    <property name="fme_feature" type="java.lang.String"/>
    <many-to-one name="salutation" entity-name="SalutationType" cascade="all" lazy="false" 
    			insert="true" update="true" not-null="false">
      <column not-null="false" unique="false" name="SALUTATIONTYPE_SALUTATION_ID"/>
    </many-to-one>
    <property name="quantity" type="java.math.BigInteger" lazy="false" not-null="false"/>
    <property name="productName" type="java.lang.String" lazy="false" not-null="false"/>
    <property name="shipDate" type="serializable" lazy="false" not-null="false"/>
    <property name="fme_mixed_text" type="java.lang.String"/>
    <property name="fme_mixed_cdata" type="java.lang.String"/>
    <property name="fme_mixed_comment" type="java.lang.String"/>
</class>

The current implementation has one limitation: the text, cdata and comment parts of a mixed content can not be longer than 255 characters, this is the default length for a string column.


Benefits of this approach

The chosen approach has a number of benefits:

  • Hidden implementation: The extensions to persist FeatureMaps are completely hidden for the application developer. He/She can use the standard EMF apis.
  • No unnecessary columns: a (nullable) column is only created for the types which can be present in a FeatureMap.Entry.
  • Advanced querying possible: foreign keys are created in the entry table for references to other classes (tables). This means that it is possible to join tables using advanced SQL or HQL queries.

Features such as cascading deletes (containment) or delete restrictions work directly and without any further extra specifications.

Back to the top