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

Difference between revisions of "EclipseLink/DesignDocs/217508"

(XMLContext's <code>primitivesByQName</code> Map)
(Use Case 3: Datatype is a Union)
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
<div style="margin:5px;float:right;border:1px solid #000000;padding:5px">__TOC__</div>
 
<div style="margin:5px;float:right;border:1px solid #000000;padding:5px">__TOC__</div>
  
= Support for Root Elements representing Simple Types =
+
= Support for Root Elements representing Datatypes =
  
 
[http://bugs.eclipse.org/217508 ER 217508]
 
[http://bugs.eclipse.org/217508 ER 217508]
Line 14: Line 14:
 
| 2008-02-01
 
| 2008-02-01
 
| Rick Barkhouse
 
| Rick Barkhouse
| Initial Draft
+
| Initial draft.
 +
|-
 +
| 2008-02-29
 +
| Rick Barkhouse
 +
| Added prototype information.
 
|}
 
|}
  
 
= Project overview =
 
= Project overview =
  
This project will add the ability for EclipseLink OX to unmarshal XML documents that have simple types as their root element.  Instead of a "rich" top-level element representing a domain object (e.g. <code>Employee</code>, <code>PurchaseOrder</code>), the root element may be a simple primitive value.
+
This project will add the ability for EclipseLink MOXy to unmarshal XML documents that have datatypes as their root element.  Instead of a "rich" top-level element representing a domain object (e.g. <code>Employee</code>, <code>PurchaseOrder</code>), the root element may be a datatype value, such as <code>String</code> or <code>Byte[]</code>.
  
 
Goals:
 
Goals:
Line 131: Line 135:
 
= Functionality =
 
= Functionality =
  
Schema (<code>primitive.xsd</code>):
+
Schema (<code>myDatatypeSchema.xsd</code>):
  
 
<source lang="xml">
 
<source lang="xml">
 
...
 
...
   <element name="aSimpleValue" type="mySimpleType"/>
+
   <element name="aValue" type="myDatatype"/>
  
   <simpleType name="mySimpleType">
+
   <simpleType name="myDatatype">
 
       <restriction base="double">
 
       <restriction base="double">
 
         <enumeration value="4"/>
 
         <enumeration value="4"/>
Line 150: Line 154:
 
</source>
 
</source>
  
== Use Case 1: Root Element is a Primitive Simple Type ==
+
== Use Case 1: Root Element is a Datatype ==
  
Instance Document (<code>primitive-1.xml</code>):
+
Instance Document (<code>instance-1.xml</code>):
  
 
<source lang="xml">
 
<source lang="xml">
 
<?xml version="1.0"?>
 
<?xml version="1.0"?>
  
<aSimpleValue>16</aSimpleValue>
+
<aValue>16</aValue>
 
</source>
 
</source>
  
Line 167: Line 171:
 
   MyProject proj = new MyProject();
 
   MyProject proj = new MyProject();
 
   XMLContext ctx = new XMLContext(proj);
 
   XMLContext ctx = new XMLContext(proj);
   Object o = ctx.createUnmarshaller().unmarshal(new File("primitive-1.xml"));
+
   Object o = ctx.createUnmarshaller().unmarshal(new File("instance-1.xml"));
 
   
 
   
 
   System.out.println("OBJECT: " + o + " ("+ o.getClass() + ")");
 
   System.out.println("OBJECT: " + o + " ("+ o.getClass() + ")");
Line 179: Line 183:
 
</source>
 
</source>
  
== Use Case 2: Unexpected Content ==
+
== Use Case 2: Element Type Doesn't Match Schema ==
  
Instance Document (<code>primitive-2.xml</code>):
+
Instance Document (<code>instance-2.xml</code>):
  
 
<source lang="xml">
 
<source lang="xml">
 
<?xml version="1.0"?>
 
<?xml version="1.0"?>
  
<aSimpleValue>
+
<aValue xsi:type="date">16</aValue>
 +
</source>
 +
 
 +
== Use Case 3: Unexpected Content ==
 +
 
 +
Instance Document (<code>instance-3.xml</code>):
 +
 
 +
<source lang="xml">
 +
<?xml version="1.0"?>
 +
 
 +
<aValue>
 
   16
 
   16
 
   <other>Something Else</other>
 
   <other>Something Else</other>
 
   Hello World!
 
   Hello World!
</aSimpleValue>
+
</aValue>
 
</source>
 
</source>
  
== Use Case 3: Element Type Doesn't Match Schema ==
+
== Use Case 4: Datatype is a Union ==
  
Instance Document (<code>primitive-3.xml</code>):
+
Instance Document (<code>instance-4.xml</code>):
  
 
<source lang="xml">
 
<source lang="xml">
 
<?xml version="1.0"?>
 
<?xml version="1.0"?>
 
<aSimpleValue xsi:type="date">16</aSimpleValue>
 
 
</source>
 
</source>
  
Line 207: Line 219:
 
Following is an overview of the prototype implementation.
 
Following is an overview of the prototype implementation.
  
== XMLContext's <code>primitivesByQName</code> Map ==
+
== XMLDatatypeDescriptor ==
  
A HashMap is added to <code>XMLContext</code> to keep track of which user-defined types represent restrictions ("subclasses") of XML primitive typesThe values of this map are instances of a new class, <code>XMLDatatypeDescriptor</code>, and keyed on the element's <code>QName</code>.
+
The information required to work with primitive data types is encapsulated in a new class, <code>XMLDatatypeDescriptor</code>.  This is far less complicated than the other types of Eclipselink descriptors, storing only the following data:
 +
 
 +
* the element's <code>QName</code>
 +
* the element's XSD schema type (also a <code>QName</code>)
 +
* the Java class that the value will be converted into
  
 
<source lang="java">
 
<source lang="java">
Line 215: Line 231:
 
...
 
...
 
public class XMLDatatypeDescriptor {
 
public class XMLDatatypeDescriptor {
   // Element's QName; also used as the key in XMLContext's primitivesByQName Map
+
   // Element's QName; also used as the key in XMLContext's datatypesByQName Map
 
   private QName qName;
 
   private QName qName;
 
   // Java class this element will be converted to
 
   // Java class this element will be converted to
Line 221: Line 237:
 
   // Element's XSD Type
 
   // Element's XSD Type
 
   private QName schemaType;
 
   private QName schemaType;
   
 
  public XMLDatatypeDescriptor(QName aQName, Class aClass, QName aSchemaType) {
 
      ...
 
  }
 
 
   ...
 
   ...
 
}
 
}
 
</source>
 
</source>
  
During unmarshal operations, if the QName of the element being unmarshalled is present in <code>primitivesByQName</code>, a new <code>XMLRootRecord</code> is created.  <code>XMLRootRecord</code> is a SAX ContentHandler, and control will be passed to it to unmarshal the primitive element:
+
== XMLContext's datatypesByQName Map ==
 +
 
 +
A hash map is added to <code>XMLContext</code> to keep track of which user-defined types represent restrictions ("subclasses") of XML primitive types.  The values of this map are <code>XMLDatatypeDescriptor</code>s, keyed on the element's <code>QName</code>.
 +
 
 +
<source lang="java">
 +
// org.eclipse.persistence.oxm.XMLContext
 +
 
 +
public class XMLContext {
 +
    ...
 +
    private Map<QName, XMLDatatypeDescriptor> datatypesByQName;
 +
    ...
 +
}
 +
</source>
 +
 
 +
During unmarshal operations, if the <code>QName</code> of the element being unmarshalled is present in <code>datatypesByQName</code>, a new <code>XMLRootRecord</code> is created.  <code>XMLRootRecord</code> is a SAX ContentHandler, and control will be passed to it to unmarshal the primitive element:
  
 
<source lang="java">
 
<source lang="java">
 
// org.eclipse.persistence.internal.oxm.record.SAXUnmarshallerHandler
 
// org.eclipse.persistence.internal.oxm.record.SAXUnmarshallerHandler
...
+
 
 
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
 
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
 
   ...
 
   ...
Line 240: Line 266:
 
   if (null == xmlDescriptor) {
 
   if (null == xmlDescriptor) {
 
       // Check to see if there is an XMLDatatypeDescriptor for this QName
 
       // Check to see if there is an XMLDatatypeDescriptor for this QName
       XMLDatatypeDescriptor wrapper = (XMLDatatypeDescriptor) xmlContext.getPrimitivesByQName().get(rootQName);
+
       XMLDatatypeDescriptor wrapper = (XMLDatatypeDescriptor) xmlContext.getDatatypesByQName().get(rootQName);
 
       if (wrapper != null) {
 
       if (wrapper != null) {
 
             XMLRootRecord rootRecord = new XMLRootRecord(wrapper.getJavaClass(), this, wrapper.getSchemaType());
 
             XMLRootRecord rootRecord = new XMLRootRecord(wrapper.getJavaClass(), this, wrapper.getSchemaType());
Line 255: Line 281:
 
This map is populated in one of three ways:
 
This map is populated in one of three ways:
  
1. '''(SDO)''' When the user defines a schema using <code>SDOXSDHelper.define()</code>:
+
1. '''(SDO)''' When the user defines a schema using <code>SDOXSDHelper.define()</code>, a new XMLDatatypeDescriptor will be created and put into the map during type generation:
  
 
<source lang="java">
 
<source lang="java">
Line 273: Line 299:
 
       XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(qn, primitiveClass, xsdTypeQN);  
 
       XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(qn, primitiveClass, xsdTypeQN);  
  
       helper.getXmlContext().getPrimitivesByQName().put(qn, wrapper);
+
       helper.getXmlContext().getDatatypesByQName().put(qn, wrapper);
 
}
 
}
 
</source>
 
</source>
  
2. '''(SDO)''' When the user specifies types directly, using <code>SDOTypeHelper.defineOpenContentProperty()</code>:
+
2. '''(SDO)''' When the user specifies types directly, using <code>SDOTypeHelper.define()</code>, a new XMLDatatypeDescriptor will be put into the map in defineOpenContentProperty():
  
 
<source lang="java">
 
<source lang="java">
Line 298: Line 324:
 
         XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(propertyQName, primitiveClass, xsdTypeQN);  
 
         XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(propertyQName, primitiveClass, xsdTypeQN);  
 
                  
 
                  
         helper.getXmlContext().getPrimitivesByQName().put(propertyQName, wrapper);
+
         helper.getXmlContext().getDatatypesByQName().put(propertyQName, wrapper);
 
       }
 
       }
 
   }
 
   }
Line 304: Line 330:
 
</source>
 
</source>
  
3. When the user explicitly adds primitive type information to <code>XMLLogin</code>.
+
3. When the user explicitly adds primitive type information to <code>XMLLogin</code> in code:
 +
 
 +
<source lang="java">
 +
XMLLogin xmlLogin = new XMLLogin();
 +
Project oxmProject = new Project(xmlLogin);
 +
 
 +
QName qname = new QName("myBase64Binary-NS", "myBase64Binary");
 +
XMLDatatypeDescriptor desc = new XMLDatatypeDescriptor(qname, Byte[].class, XMLConstants.BASE_64_BINARY_QNAME);
 +
xmlLogin.addXMLDatatypeDescriptor(desc);
 +
 
 +
XMLContext xmlContext = new XMLContext(oxmProject);
 +
...
 +
</source>
 +
 
 +
== Project Metadata ==
 +
 
 +
<code>XMLDatatypeDescriptors</code> are persisted to XML as part of the <code>XMLLogin</code> element, and are accessed through the public API on <code>XMLLogin</code> shown above.  An example of a project containing primitive information:
 +
 
 +
<source lang="xml">
 +
<?xml version="1.0" encoding="UTF-8"?>
 +
<eclipselink:object-persistence version="Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080227)" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 +
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:eclipselink="http://xmlns.oracle.com/ias/xsds/eclipselink">
 +
  <eclipselink:name>My OXM Project</eclipselink:name>
 +
  <eclipselink:login xsi:type="eclipselink:xml-login">
 +
      <eclipselink:platform-class>org.eclipse.persistence.oxm.platform.SAXPlatform</eclipselink:platform-class>
 +
      <eclipselink:user-name></eclipselink:user-name>
 +
      <eclipselink:password></eclipselink:password>
 +
      <eclipselink:datatype-descriptors>
 +
        <eclipselink:datatype-descriptor>
 +
            <eclipselink:java-class>java.lang.Float</eclipselink:java-class>
 +
            <eclipselink:schema-type>{http://www.w3.org/2001/XMLSchema}float</eclipselink:schema-type>
 +
            <eclipselink:qname>{myFloat-NS}myFloat</eclipselink:qname>
 +
        </eclipselink:datatype-descriptor>
 +
      </eclipselink:datatype-descriptors>
 +
  </eclipselink:login>
 +
</eclipselink:object-persistence>
 +
</source>
  
 
= Documentation =
 
= Documentation =

Latest revision as of 15:07, 3 March 2008

Support for Root Elements representing Datatypes

ER 217508

Document History

Date Author Version Description & Notes
2008-02-01 Rick Barkhouse Initial draft.
2008-02-29 Rick Barkhouse Added prototype information.

Project overview

This project will add the ability for EclipseLink MOXy to unmarshal XML documents that have datatypes as their root element. Instead of a "rich" top-level element representing a domain object (e.g. Employee, PurchaseOrder), the root element may be a datatype value, such as String or Byte[].

Goals:

  • Allow the user to unmarshal XML documents that contain simple type root elements.

Concepts

The following concepts are used in this document:

  • Simple Type - one of the basic, "built-in" types available in XML Schema. These may represent basic Java primitives (such as xsd:boolean and xsd:float), or more robust types (xsd:hexBinary, xsd:dateTime, xsd:QName). XML Schema has two categories of simple types; Primitive Types (the most basic of data type), and Derived Types (other simple types that are defined in terms of primitive types).

XML Schema Primitive Data Types
string boolean decimal float double duration dateTime
time date gYearMonth gYear gMonthDay gDay gMonth
hexBinary base64Binary anyURI QName NOTATION
XML Schema Derived Data Types
normalizedString token language NMTOKEN NMTOKENS Name NCName
ID IDREF IDREFS ENTITY ENTITIES integer nonPositiveInteger
negativeInteger long int short byte nonNegativeInteger unsignedLong
unsignedInt unsignedShort unsignedByte positiveInteger

For more information see: http://www.w3.org/TR/xmlschema-2/#built-in-datatypes

Requirements

The requirements for this project are as follows:

  • Support unmarshalling of XML instance documents that contain Simple Type (both primitive and derived) root elements.
  • Support marshalling "primitive" Java objects to XML.
  • Ensure that the proper ConversionManager is used when converting XML to Java.
  • Ensure that the proper ClassLoader is used when converting values.


For example, the JAXB TCK uses the following types of test documents:

XML Schema - The schema contains a single element, a restriction (extension) of the base64Binary simple type:

<schema ...>
...
   <element name="NISTSchema-base64Binary-enumeration" type="nist:NISTSchema-base64Binary-enumeration-Type"/>
 
   <simpleType name="NISTSchema-base64Binary-enumeration-Type">
      <restriction base="base64Binary">
         <enumeration value="bHlsY2JmaXFjaW9ubmg="/>
      </restriction>
   </simpleType>
</schema>

XML Instance Document - The document contains a single element with value zGk=:

<NISTSchema-base64Binary-enumeration
    xmlns="NISTSchema-base64Binary-enumeration-NS"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="NISTSchema-base64Binary-enumeration-NS enumeration.xsd">zGk=</NISTSchema-base64Binary-enumeration>

Functionality

Schema (myDatatypeSchema.xsd):

...
   <element name="aValue" type="myDatatype"/>
 
   <simpleType name="myDatatype">
      <restriction base="double">
         <enumeration value="4"/>
         <enumeration value="8"/>
         <enumeration value="15"/>
         <enumeration value="16"/>
         <enumeration value="23"/>
         <enumeration value="42"/>
      </restriction>
   </simpleType>
...

Use Case 1: Root Element is a Datatype

Instance Document (instance-1.xml):

<?xml version="1.0"?>
 
<aValue>16</aValue>

EclipseLink Code:

import org.eclipse.persistence.oxm.XMLContext;
...
   MyProject proj = new MyProject();
   XMLContext ctx = new XMLContext(proj);
   Object o = ctx.createUnmarshaller().unmarshal(new File("instance-1.xml"));
 
   System.out.println("OBJECT: " + o + " ("+ o.getClass() + ")");
...

Result:

OBJECT: 16.0 (class java.lang.Double)

Use Case 2: Element Type Doesn't Match Schema

Instance Document (instance-2.xml):

<?xml version="1.0"?>
 
<aValue xsi:type="date">16</aValue>

Use Case 3: Unexpected Content

Instance Document (instance-3.xml):

<?xml version="1.0"?>
 
<aValue>
   16
   <other>Something Else</other>
   Hello World!
</aValue>

Use Case 4: Datatype is a Union

Instance Document (instance-4.xml):

<?xml version="1.0"?>

Prototype

Following is an overview of the prototype implementation.

XMLDatatypeDescriptor

The information required to work with primitive data types is encapsulated in a new class, XMLDatatypeDescriptor. This is far less complicated than the other types of Eclipselink descriptors, storing only the following data:

  • the element's QName
  • the element's XSD schema type (also a QName)
  • the Java class that the value will be converted into
// org.eclipse.persistence.oxm.XMLDatatypeDescriptor
...
public class XMLDatatypeDescriptor {
   // Element's QName; also used as the key in XMLContext's datatypesByQName Map
   private QName qName;
   // Java class this element will be converted to
   private Class javaClass;
   // Element's XSD Type
   private QName schemaType;
   ...
}

XMLContext's datatypesByQName Map

A hash map is added to XMLContext to keep track of which user-defined types represent restrictions ("subclasses") of XML primitive types. The values of this map are XMLDatatypeDescriptors, keyed on the element's QName.

// org.eclipse.persistence.oxm.XMLContext
 
public class XMLContext {
    ...
    private Map<QName, XMLDatatypeDescriptor> datatypesByQName;
    ...
}

During unmarshal operations, if the QName of the element being unmarshalled is present in datatypesByQName, a new XMLRootRecord is created. XMLRootRecord is a SAX ContentHandler, and control will be passed to it to unmarshal the primitive element:

// org.eclipse.persistence.internal.oxm.record.SAXUnmarshallerHandler
 
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
   ...
   XMLDescriptor xmlDescriptor = xmlContext.getDescriptor(rootQName);
   ...
   if (null == xmlDescriptor) {
      // Check to see if there is an XMLDatatypeDescriptor for this QName
      XMLDatatypeDescriptor wrapper = (XMLDatatypeDescriptor) xmlContext.getDatatypesByQName().get(rootQName);
      if (wrapper != null) {
            XMLRootRecord rootRecord = new XMLRootRecord(wrapper.getJavaClass(), this, wrapper.getSchemaType());
            rootRecord.setSession((AbstractSession) xmlContext.getSession(wrapper));
            rootRecord.startElement(namespaceURI, localName, qName, atts);
            xmlReader.setContentHandler(rootRecord);
            return;
      }
   }
   ...
}

This map is populated in one of three ways:

1. (SDO) When the user defines a schema using SDOXSDHelper.define(), a new XMLDatatypeDescriptor will be created and put into the map during type generation:

// org.eclipse.persistence.sdo.helper.SDOTypesGenerator
 
private void addRootElementToDescriptor(SDOProperty p, String targetNamespace, String xsdName) {
   if (!p.getType().isDataType()) {
      ...
   } else {
      // This must be a primitive, so add to XMLContext's primitivesByQName map
      SDOXMLHelper helper = (SDOXMLHelper) ((SDOType)p.getType()).getHelperContext().getXMLHelper();
 
      QName qn = new QName(targetNamespace, xsdName);
      Class primitiveClass = p.getType().getInstanceClass();
      QName xsdTypeQN = ((SDOType)p.getType()).getXsdType();
 
      XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(qn, primitiveClass, xsdTypeQN); 
 
      helper.getXmlContext().getDatatypesByQName().put(qn, wrapper);
}

2. (SDO) When the user specifies types directly, using SDOTypeHelper.define(), a new XMLDatatypeDescriptor will be put into the map in defineOpenContentProperty():

// org.eclipse.persistence.sdo.helper.delegates.SDOTypeHelperDelegate
 
private void defineOpenContentProperty(String propertyUri, String propertyName, Property property) {
   if (propertyUri != null) {            
      ...
      XMLDescriptor aDescriptor = ((SDOType)property.getType()).getXmlDescriptor();
      ...
      if (aDescriptor != null) {
         ...
      } else {
         // This must be a primitive, so add to XMLContext's primitivesByQName map
         SDOXMLHelper helper = (SDOXMLHelper) ((SDOType)property.getType()).getHelperContext().getXMLHelper();
 
         Class primitiveClass = property.getType().getInstanceClass();
         QName xsdTypeQN = ((SDOType)property.getType()).getXsdType();
 
         XMLDatatypeDescriptor wrapper = new XMLDatatypeDescriptor(propertyQName, primitiveClass, xsdTypeQN); 
 
         helper.getXmlContext().getDatatypesByQName().put(propertyQName, wrapper);
      }
   }
}

3. When the user explicitly adds primitive type information to XMLLogin in code:

XMLLogin xmlLogin = new XMLLogin();
Project oxmProject = new Project(xmlLogin);
 
QName qname = new QName("myBase64Binary-NS", "myBase64Binary"); 
XMLDatatypeDescriptor desc = new XMLDatatypeDescriptor(qname, Byte[].class, XMLConstants.BASE_64_BINARY_QNAME);
xmlLogin.addXMLDatatypeDescriptor(desc);
 
XMLContext xmlContext = new XMLContext(oxmProject);
...

Project Metadata

XMLDatatypeDescriptors are persisted to XML as part of the XMLLogin element, and are accessed through the public API on XMLLogin shown above. An example of a project containing primitive information:

<?xml version="1.0" encoding="UTF-8"?>
<eclipselink:object-persistence version="Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080227)" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:eclipselink="http://xmlns.oracle.com/ias/xsds/eclipselink">
   <eclipselink:name>My OXM Project</eclipselink:name>
   <eclipselink:login xsi:type="eclipselink:xml-login">
      <eclipselink:platform-class>org.eclipse.persistence.oxm.platform.SAXPlatform</eclipselink:platform-class>
      <eclipselink:user-name></eclipselink:user-name>
      <eclipselink:password></eclipselink:password>
      <eclipselink:datatype-descriptors>
         <eclipselink:datatype-descriptor>
            <eclipselink:java-class>java.lang.Float</eclipselink:java-class>
            <eclipselink:schema-type>{http://www.w3.org/2001/XMLSchema}float</eclipselink:schema-type>
            <eclipselink:qname>{myFloat-NS}myFloat</eclipselink:qname>
         </eclipselink:datatype-descriptor>
      </eclipselink:datatype-descriptors>
   </eclipselink:login>
</eclipselink:object-persistence>

Documentation

EclipseLink User Documentation should be updated to demonstrate how documents containing Simple Type root elements are supported.

Open Issues

Decisions

Issue # Owner Description / Notes

Future Considerations

Issue # Description / Notes Decision

Back to the top