Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/Development/2.1/DynamicMOXy/296967/BootstrapFromOXM
Contents
- 1 Dynamic MOXy - Bootstrapping from EclipseLink Externalized Metadata (OXM)
Dynamic MOXy - Bootstrapping from EclipseLink Externalized Metadata (OXM)
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:
- The OXM file is parsed by JAXBContextFactory to generate an org.eclipse.persistence.jaxb.xmlmodel.XmlBindings object.
- The XmlBindings are inspected and an EclipseLink javamodel representing this information is created.
- This collection of EclipseLink javamodel classes is then passed to org.eclipse.persistence.jaxb.compiler.Generator, which generates a standard EclipseLink project.
- A Dynamic Project is then created from the standard project via DynamicTypeBuilder.loadDynamicProject().
- 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="UTF-8"?>
- Text.
- Text.
Limitations
- Text.
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 |