Jump to: navigation, search

EclipseLink/DesignDocs/217508

< EclipseLink‎ | DesignDocs
Revision as of 14:07, 3 March 2008 by Rick.barkhouse.oracle.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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