Jump to: navigation, search

EclipseLink/Development/2.1/DynamicMOXy/296967/BootstrapFromOXM

Dynamic MOXy - Bootstrapping from EclipseLink Externalized Metadata (OXM)

ER 321152


Overview

The purpose of this feature is to allow the user to bootstrap EclipseLink Dynamic JAXB from an EclipseLink externalized metadata file (eclipselink-oxm.xml). This is a continuation of the Dynamic JAXB features introduced in EclipseLink 2.1.0.

Support for bootstrapping a DynamicJAXBContext from an EclipseLink Externalized Metadata file (OXM) will be implemented in multiple phases. For each phase, OXM files will be created that will exercise the associated annotations. A DynamicJAXBContext will be created from each OXM file, and an example object will be marshalled. The resulting document will be inspected to ensure that it was marshalled properly, according to the annotations being tested.

See also: Dynamic JAXB Documentation


Generating a DynamicJAXBContext with OXM

A DynamicJAXBContext is created from an OXM file in the following way:

  1. The OXM file is parsed by JAXBContextFactory to generate an org.eclipse.persistence.jaxb.xmlmodel.XmlBindings object.
  2. The XmlBindings are inspected and an EclipseLink javamodel representing this information is created.
  3. This collection of EclipseLink javamodel classes is then passed to org.eclipse.persistence.jaxb.compiler.Generator, which generates a standard EclipseLink project.
  4. A Dynamic Project is then created from the standard project via DynamicTypeBuilder.loadDynamicProject().
  5. An org.eclipse.persistence.oxm.XMLContext is then created from the Dynamic Project, and a DynamicJAXBContext is returned backed by this XMLContext.


Public API

The user will be able to bootstrap a DynamicJAXBContext using the following method in DynamicJAXBContextFactory:

/**
 * Create a <tt>DynamicJAXBContext</tt>, using an EclipseLink OXM file as the metadata source.
 *
 * @param classLoader
 *      The application's current class loader, which will be used to first lookup
 *      classes to see if they exist before new <tt>DynamicTypes</tt> are generated.  Can be
 *      <tt>null</tt>, in which case <tt>Thread.currentThread().getContextClassLoader()</tt> will be used.
 * @param properties
 *      Map of properties to use when creating a new <tt>DynamicJAXBContext</tt>.  This map must
 *      contain a key of JAXBContext.ECLIPSELINK_OXM_XML_KEY, with a value of Map<String, Source>,
 *      where String is the package name and Source is the metadata file for that package.
 *
 * @return
 *      A new instance of <tt>DynamicJAXBContext</tt>.
 *
 * @throws JAXBException
 *      if an error was encountered while creating the <tt>DynamicJAXBContext</tt>.
 */
public static DynamicJAXBContext createContextFromOXM(ClassLoader classLoader, Map<String, ?> properties) throws JAXBException

An example of setting up the properties to point to the user's OXM file:

InputStream iStream = classLoader.getResourceAsStream("/myapp/data/eclipselink/eclipselink-oxm.xml");
HashMap<String, Source> metadataSourceMap = new HashMap<String, Source>();
metadataSourceMap.put("mynamespace", new StreamSource(iStream));
 
Map<String, Map<String, Source>> properties = new HashMap<String, Map<String, Source>>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadataSourceMap);
 
DynamicJAXBContext dContext = DynamicJAXBContextFactory.createContextFromOXM(classLoader, properties);
DynamicEntity person = dContext.newDynamicEntity("mynamespace.Person");
...

Proposal: should we provide a convenience method that takes a single package name and OXM file, to save the user having to construct two HashMaps? eg:

createContextFromOXM(ClassLoader classLoader, String packageName, InputStream oxmStream, Map<String, ?> properties)
 
or
 
createContextFromOXM(ClassLoader classLoader, String packageName, StreamSource oxmSource, Map<String, ?> properties)

Development Phases

Phase 1

Annotation XML Metadata Tag Package Type Field Method
XmlNs xml-ns        
XmlSchema xml-schema X      
XmlSeeAlso xml-see-also   X    


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
    <xml-schema element-form-default="QUALIFIED" attribute-form-default="QUALIFIED" namespace="mynamespace">
        <xml-ns prefix="ns1" namespace-uri="mynamespace"/>
        <xml-ns prefix="xsd" namespace-uri="http://www.w3.org/2001/XMLSchema"/>
        <xml-ns prefix="xsi" namespace-uri="http://www.w3.org/2001/XMLSchema-instance"/>        
    </xml-schema>
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-attribute java-attribute="id" type="java.lang.Integer"/>
                <xml-element java-attribute="name" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

Limitations

  • XmlSeeAlso is a purely Java-object annotation, it has no effect on marshalled XML. It is therefore not applicable to Dynamic JAXB.



Phase 2

Annotation XML Metadata Tag Package Type Field Method
XmlRootElement xml-root-element   X    
XmlType xml-type   X    


The following example OXM files demonstrate how these annotations can be specified:

Example OXM

xml-root-element

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="individuo"/>
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>
  • When the DynamicEntity is marshalled to XML, the document's root element will be <individuo>.

xml-type

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <xml-type prop-order="id firstName lastName phoneNumber email"/>
            <java-attributes>
                <xml-element java-attribute="firstName" type="java.lang.String"/>
                <xml-element java-attribute="phoneNumber" type="java.lang.String"/>
                <xml-element java-attribute="id" type="java.lang.Integer"/>
                <xml-element java-attribute="email" type="java.lang.String"/>
                <xml-element java-attribute="lastName" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>
  • When the DynamicEntity is marshalled to XML, the elements will appear in the order specified in the xml-type element.



Phase 3

Annotation XML Metadata Tag Package Type Field Method
XmlAttribute xml-attribute     X X
XmlElement xml-element     X X


The following example OXM files demonstrate how these annotations can be specified:

Example OXM

xml-attribute

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-attribute java-attribute="id" type="java.lang.Integer"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

xml-element

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="type" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>



Phase 4

Annotation XML Metadata Tag Package Type Field Method
XmlList xml-list     X X
XmlValue xml-value X X
XmlAnyElement xml-any-element     X X
XmlAnyAttribute xml-any-attribute     X X
XmlMixed xml-mixed     X X


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

xml-list

<?xml version="1.0" encoding="UTF-8"?>

xml-value

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-element java-attribute="salary" type="mynamespace.CdnCurrency"/>
            </java-attributes>
        </java-type>
 
        <java-type name="mynamespace.CdnCurrency">
            <java-attributes>
                <xml-value java-attribute="amount" type="java.math.BigDecimal"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

xml-any-attribute

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-any-attribute java-attribute="otherAttributes"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

xml-any-element

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-any-element java-attribute="any" lax="true"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

xml-list

<?xml version="1.0" encoding="UTF-8"?>

Limitations

  • Text.



Phase 5

Annotation XML Metadata Tag Package Type Field Method
XmlID
xml-id     X X
XmlIDREF
xml-idref   X
X


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Data">
            <xml-root-element name="data"/>
            <java-attributes>
                <xml-element java-attribute="person" type="mynamespace.Person"/>
                <xml-element java-attribute="company" type="mynamespace.Company"/>
            </java-attributes>
        </java-type>
 
        <java-type name="mynamespace.Person">
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-element java-attribute="company" type="mynamespace.Company" xml-idref="true"/>
            </java-attributes>
        </java-type>        
 
        <java-type name="mynamespace.Company">
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-element java-attribute="address" type="java.lang.String"/>
                <xml-attribute java-attribute="id" type="java.lang.Integer" xml-id="true"/>
            </java-attributes>
        </java-type>        
    </java-types>
 
</xml-bindings>



Phase 6

Annotation XML Metadata Tag Package Type Field Method
XmlElements xml-elements     X X
XmlElementRef xml-element-ref     X X


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="UTF-8"?>
  • Text.
  • Text.

Limitations

  • Text.



Phase 7

Annotation XML Metadata Tag Package Type Field Method
XmlSchemaType xml-schema-type X   X X


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings ...>
 
    ...
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="dateOfBirth">
                    <xml-schema-type name="date"/>
                </xml-element>
            </java-attributes>
        </java-type>        
    </java-types>
 
</xml-bindings>
  • the dateOfBirth will be written out as an "xs:date" element (corresponding to a javax.xml.datatype.XMLGregorianCalendar in Java).



Phase 8

Annotation XML Metadata Tag Package Type Field Method
XmlEnum xml-enum   X    
XmlEnumValue xml-enum-value     X
X


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="UTF-8"?>
  • Text.
  • Text.

Limitations

  • Text.



Phase 9

Annotation XML Metadata Tag Package Type Field Method
XmlElementDecl xml-element-decl     X X
XmlRegistry xml-registry   X    


The following example OXM file demonstrates how these annotations can be specified:

Example OXM

<?xml version="1.0" encoding="UTF-8"?>
  • Text.
  • Text.

Limitations

  • Text.



Design

When constructing a DynamicJAXBContext from an OXM file, we will first parse the metadata in order to create our own EclipseLink JavaModel classes, then pass this code model into an EclipseLink Generator (org.eclipse.persistence.jaxb.compiler.Generator) to generate an EclipseLink project. After we have created this project, we can use the DynamicTypeBuilder to create a dynamic project, generating Java classes in memory along the way.

First, we use an EclipseLink utility method to parse the OXM file and return an XmlBindings object, and use it to create a collection of OXMJavaClassImpl objects:

Map<String, XmlBindings> bindings = JAXBContextFactory.getXmlBindingsFromProperties(properties, classLoader);
 
JavaClass[] elinkClasses = createClassModelFromOXM(bindings);
 
...
private JavaClass[] createClassModelFromOXM(Map<String, XmlBindings> bindings) throws JAXBException {
    List<OXMJavaClassImpl> oxmJavaClasses = new ArrayList<OXMJavaClassImpl>();
 
    Iterator<String> keys = bindings.keySet().iterator();
 
    while (keys.hasNext()) {
        XmlBindings b = bindings.get(keys.next());
 
        List<JavaType> javaTypes = b.getJavaTypes().getJavaType();
        for (Iterator<JavaType> iterator = javaTypes.iterator(); iterator.hasNext();) {
            JavaType type = iterator.next();
            oxmJavaClasses.add(new OXMJavaClassImpl(type));
        }
    }
    ...

At this point, we can instantiate a Generator and obtain a "dry" EclipseLink project, which we can then turn into a dynamic project with generated in-memory classes. Finally, we create and store an XMLContext and a DynamicHelper:

// Use the JavaModel to setup a Generator to generate an EclipseLink project
OXMJavaModelImpl javaModel = new OXMJavaModelImpl(classLoader, elinkClasses);
OXMJavaModelInputImpl javaModelInput = new OXMJavaModelInputImpl(elinkClasses, javaModel);
Generator g = new Generator(javaModelInput, bindings, dynamicClassLoader, null);
 
Project p = null;
Project dp = null;
try {
    p = g.generateProject();
    // Clear out InstantiationPolicy because it refers to ObjectFactory, which we won't be using
    Vector<ClassDescriptor> descriptors = (Vector<ClassDescriptor>) p.getOrderedDescriptors();
    for (ClassDescriptor classDescriptor : descriptors) {
        classDescriptor.setInstantiationPolicy(new InstantiationPolicy());
    }
    dp = DynamicTypeBuilder.loadDynamicProject(p, null, dynamicClassLoader);
} catch (Exception e) {
    throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(e));
}
 
this.xmlContext = new XMLContext(dp);
 
List<Session> sessions = (List<Session>) this.xmlContext.getSessions();
for (Object session : sessions) {
    this.helpers.add(new DynamicHelper((DatabaseSession) session));
}


Testing

Annotation Tests

A series of tests will be added to test each of the annotations listed in the phases above. These tests will use eclipselink-oxm.xml files tailored to exercise a specific annotation. In each test, a DynamicJAXBContext will be created for the OXM file, and new DynamicEntities will be created and marshalled out to a org.w3c.dom.Document object. The nodes of the Document can then be examined to make sure they adhere to the configuration made in the OXM file.

For example, to test the XmlSchema's element-form-default and attribute-form-default options:

Example OXM File:

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
    <xml-schema element-form-default="QUALIFIED" attribute-form-default="QUALIFIED" namespace="mynamespace">
        <xml-ns prefix="ns1" namespace-uri="mynamespace"/>
        <xml-ns prefix="xsd" namespace-uri="http://www.w3.org/2001/XMLSchema"/>
        <xml-ns prefix="xsi" namespace-uri="http://www.w3.org/2001/XMLSchema-instance"/>        
    </xml-schema>
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-attribute java-attribute="id" type="java.lang.Integer"/>
                <xml-element java-attribute="name" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

Test Case:

public void testXmlSchemaQualified() throws Exception {
    // ... BUILD properties MAP CONTAINING LINK TO THIS TEST'S OXM FILE ...
 
    jaxbContext = DynamicJAXBContextFactory.createContextFromOXM(classLoader, properties);
 
    DynamicEntity person = jaxbContext.newDynamicEntity(PACKAGE + "." + PERSON);
    assertNotNull("Could not create Dynamic Entity.", person);
    person.set("id", 456);
    person.set("name", "Bob Dobbs");
 
    Document marshalDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    jaxbContext.createMarshaller().marshal(person, marshalDoc);
 
    // Make sure "targetNamespace" was interpreted properly.
    Node node = marshalDoc.getChildNodes().item(0);
    assertEquals("Target Namespace was not set as expected.", "mynamespace", node.getNamespaceURI());
 
    // Make sure "elementFormDefault" was interpreted properly.
    // elementFormDefault=qualified, so the root node, the
    // root node's attribute, and the child node should all have a prefix.
    assertNotNull("Root node did not have namespace prefix as expected.", node.getPrefix());
 
    Node attr = node.getAttributes().item(0);
    assertNotNull("Attribute did not have namespace prefix as expected.", attr.getPrefix());
 
    Node childNode = node.getChildNodes().item(0);
    assertNotNull("Child node did not have namespace prefix as expected.", childNode.getPrefix());
}

Model Tests

A second set of tests will take a more model-centric approach, using OXM files that contain larger domain models to exercise MOXy's mappings functionality. For example, to test Collection mappings, an example OXM will define a series of JavaTypes that use collections:

Example OXM File:

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
    <xml-schema element-form-default="QUALIFIED" attribute-form-default="QUALIFIED" namespace="mynamespace">
        <xml-ns prefix="ns1" namespace-uri="mynamespace"/>
        <xml-ns prefix="xsd" namespace-uri="http://www.w3.org/2001/XMLSchema"/>
        <xml-ns prefix="xsi" namespace-uri="http://www.w3.org/2001/XMLSchema-instance"/>
    </xml-schema>
 
    <java-types>
        <java-type name="mynamespace.Person">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-element java-attribute="name" type="java.lang.String"/>
                <xml-element java-attribute="address" type="mynamespace.Address"/>
                <xml-element java-attribute="addresses" type="mynamespace.Address" xml-list="true"/>
                <xml-element java-attribute="phoneNumber" type="mynamespace.PhoneNumber"/>
                <xml-element java-attribute="phoneNumbers" type="mynamespace.PhoneNumber" xml-list="true"/>
            </java-attributes>
        </java-type>
        <java-type name="mynamespace.Address">
            ...
        </java-type>
        <java-type name="mynamespace.PhoneNumber">
            ...
        </java-type>
    </java-types>
 
</xml-bindings>

Test Case:

public void testUnmarshal() throws JAXBException {
    // ... BUILD properties MAP CONTAINING LINK TO THIS TEST'S OXM FILE ...
 
    // ... BUILD xmlStream POINTING TO TEST INSTANCE DOCUMENT ...
 
    jaxbContext = DynamicJAXBContextFactory.createContextFromOXM(classLoader, properties);
 
    JAXBElement<DynamicEntity> jaxbElement = (JAXBElement<DynamicEntity>) jaxbContext.createUnmarshaller().unmarshal(xmlStream);
 
    DynamicEntity customer = jaxbElement.getValue();
    assertNotNull("Could not create Dynamic Entity.", customer);
 
    List<DynamicEntity> addresses = customer.<List<DynamicEntity>>get("addresses");
    assertEquals("addresses list did not contain the correct number of Addresses.", 2, addresses.size());
    DynamicEntity firstAddress = addresses.get(0);
    assertEquals("First Address city was not correct", "Any Town", firstAddress.get("city"));
 
    List<DynamicEntity> phoneNumbers = customer.<List<DynamicEntity>>get("phoneNumbers");
    assertEquals("phoneNumbers list did not contain the correct number of PhoneNumbers.", 3, phoneNumbers.size());
    DynamicEntity firstPhoneNumber = phoneNumbers.get(0);
    assertEquals("First PhoneNumber type was not correct", "work", firstPhoneNumber.get("type"));
 
    // ... ETC ...
}


Document History

Date Author Version Description & Notes                                                                                
2010/07/26 Rick Barkhouse Initial contribution
2010/07/28 Rick Barkhouse Formatting, expanded overview, added bugzilla and doc links
2010/07/30 Rick Barkhouse Added Testing section
2010/08/03 Rick Barkhouse Added Public API section
2010/08/11 Rick Barkhouse Added information on Phases 1-3