Skip to main content

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.

Jump to: navigation, search

Difference between revisions of "EclipseLink/Development/DynamicOXM"

 
(12 intermediate revisions by the same user not shown)
Line 10: Line 10:
  
 
== Document History  ==
 
== Document History  ==
 
+
{|{{BMTableStyle}}
{|
+
|-{{BMTHStyle}}
|-
+
 
! Date  
 
! Date  
 
! Author  
 
! Author  
! Version Description & Notes
+
! Version
 +
! Description & Notes
 
|-
 
|-
 
| 091117  
 
| 091117  
 
| Rick Barkhouse  
 
| Rick Barkhouse  
 
| 1.0
 
| 1.0
 +
|
 +
|-
 +
| 091125
 +
| Rick Barkhouse
 +
| 1.1
 +
| Changed XMLBackReferenceMapping to XMLInverseReferenceMapping
 +
|-
 +
| 091127
 +
| Rick Barkhouse
 +
| 1.11
 +
| Code formatting
 +
|-
 +
| 091201
 +
| Rick Barkhouse
 +
| 1.2
 +
| Added example code showing equivalent annotations
 
|}
 
|}
  
<br>  
+
<br>
  
 
__TOC__  
 
__TOC__  
Line 28: Line 44:
 
== Overview  ==
 
== Overview  ==
  
The first step in creating a JPA-JAXB meet-in-the-middle solution is to enable EclipseLink OXM to use Dynamic Persistence, a.k.a. processing Projects, Mappings &amp; Descriptors without having concrete Java classes available. This work builds off of the initial Dynamic Persistence functionality (<code>org.eclipse.persistence.dynamic</code>) current used by the JPA component. By supplying a <code>DynamicClassLoader</code> and loading the EclipseLink project by way of <code>DynamicTypeBuilder.loadDynamicProject()</code>, we can inject Dynamic Persistence functionality into EclipseLink OXM in a fairly transparent way.  
+
The first step in creating a JPA-JAXB end-to-end solution is to enable EclipseLink OXM to use Dynamic Persistence, a.k.a. processing Projects, Mappings &amp; Descriptors without having concrete Java classes available. This work builds off of the initial Dynamic Persistence functionality (<code>org.eclipse.persistence.dynamic</code>) current used by the JPA component. By supplying a <code>DynamicClassLoader</code> and loading the EclipseLink project by way of <code>DynamicTypeBuilder.loadDynamicProject()</code>, we can inject Dynamic Persistence functionality into EclipseLink OXM in a fairly transparent way.  
  
 
== Prototype  ==
 
== Prototype  ==
Line 44: Line 60:
 
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(sysClassLoader);
 
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(sysClassLoader);
  
Project dynamicTestProject = DynamicTypeBuilder.loadDynamicProject((Project) new DynamicTestProject(), null, dynamicClassLoader);
+
Project noClassesTestProject = new DynamicTestProject();
 +
Project dynamicTestProject =
 +
  DynamicTypeBuilder.loadDynamicProject(noClassesTestProject , null, dynamicClassLoader);
  
 
setProject(dynamicTestProject);
 
setProject(dynamicTestProject);
 
</source>  
 
</source>  
 
 
  
 
We can also load a project.xml file and use it to create our Dynamic project.  
 
We can also load a project.xml file and use it to create our Dynamic project.  
Line 58: Line 74:
  
 
<source lang="java5">
 
<source lang="java5">
public static Project loadDynamicProject(Project project, DatabaseLogin login, DynamicClassLoader dynamicClassLoader)
+
public static Project loadDynamicProject(Project project,  
 +
                                        DatabaseLogin login,  
 +
                                        DynamicClassLoader dynamicClassLoader)
 
</source>  
 
</source>  
  
Line 82: Line 100:
 
phone.setAttributeName("phoneNumbers");
 
phone.setAttributeName("phoneNumbers");
 
phone.setXPath("phone-numbers/number");
 
phone.setXPath("phone-numbers/number");
phone.setReferenceClassName("rick.dynamic.XPhoneNumber");
+
phone.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XPhoneNumber");
 
...
 
...
 
phone.getBiDirectionalPolicy().setBidirectionalTargetAttributeName("owningEmployee");
 
phone.getBiDirectionalPolicy().setBidirectionalTargetAttributeName("owningEmployee");
Line 92: Line 110:
 
The problem is that when Dynamic Types are being created, class attributes are created for each of the descriptor's mappings; and since the <code>owningEmployee</code> attribute is not mapped on the <code>Phone</code> descriptor, one would not be generated on the dynamic <code>Phone</code> class.  
 
The problem is that when Dynamic Types are being created, class attributes are created for each of the descriptor's mappings; and since the <code>owningEmployee</code> attribute is not mapped on the <code>Phone</code> descriptor, one would not be generated on the dynamic <code>Phone</code> class.  
  
The solution was to create a new type of mapping (tentatively <code>XMLBackReferenceMapping</code>) that can be set on the target descriptor, and delegate it's functionality back to the original back-pointer code in <code>XMLCompositeObjectMapping</code>:  
+
The solution was to create a new type of mapping (tentatively <code>XMLInverseReferenceMapping</code>) that can be set on the target descriptor, and delegate it's functionality back to the original back-pointer code in <code>XMLCompositeObjectMapping</code>:  
  
 
<source lang="java5">
 
<source lang="java5">
public class XMLBackReferenceMapping extends AggregateMapping implements ContainerMapping {
+
public class XMLInverseReferenceMapping extends AggregateMapping implements ContainerMapping {
 
...
 
...
 
@Override
 
@Override
 
public void postInitialize(AbstractSession session) throws DescriptorException {
 
public void postInitialize(AbstractSession session) throws DescriptorException {
 
   // Get the corresponding mapping from the reference descriptor and set up the  
 
   // Get the corresponding mapping from the reference descriptor and set up the  
   // back-pointer policy.
+
   // inverse mapping.
   DatabaseMapping mapping = getReferenceDescriptor().getMappingForAttributeName(this.mappedBy);
+
   DatabaseMapping mapping =
 +
      getReferenceDescriptor().getMappingForAttributeName(this.mappedBy);
  
 
   if (mapping instanceof XMLCompositeCollectionMapping) {
 
   if (mapping instanceof XMLCompositeCollectionMapping) {
 
       XMLCompositeCollectionMapping oppositeMapping = (XMLCompositeCollectionMapping) mapping;
 
       XMLCompositeCollectionMapping oppositeMapping = (XMLCompositeCollectionMapping) mapping;
       oppositeMapping.setBackReferenceMapping(this);
+
       oppositeMapping.setInverseReferenceMapping(this);
 
   }
 
   }
 +
  ...
 
}
 
}
 
...
 
...
 
</source>  
 
</source>  
 +
 +
<b>NOTE: In moving the bi-directional aspect from a property on the source descriptor to its own XML Mapping, it no longer seemed appropriate to use "bi-directional" terminology, which is why we chose <code>XMLInverseReferenceMapping</code>.  On 091124 in a conference call with Shaun we changed this from the original <code>XMLBackReferenceMapping</code> to <code>XMLInverseReferenceMapping</code>.</b>
  
 
With these changes, back-pointers are now configured as follows:  
 
With these changes, back-pointers are now configured as follows:  
Line 124: Line 146:
  
 
// PHONE Descriptor
 
// PHONE Descriptor
XMLBackReferenceMapping owningEmployee = new XMLBackReferenceMapping();
+
XMLInverseReferenceMapping owningEmployee = new XMLInverseReferenceMapping();
 
owningEmployee.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XEmployee");
 
owningEmployee.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XEmployee");
 
owningEmployee.setMappedBy("phoneNumbers");
 
owningEmployee.setMappedBy("phoneNumbers");
Line 133: Line 155:
 
</source>  
 
</source>  
  
What was previously <code>containerAttributeName/containerGetMethodName/containerSetMethodName</code> on <code>XMLCompositeCollectionMapping</code> is now accessed through that mapping's new <code>backReferenceMapping</code> field. The "opposite property" is specified with the <code>setMappedBy()</code> API.  
+
What was previously <code>containerAttributeName/containerGetMethodName/containerSetMethodName</code> on <code>XMLCompositeCollectionMapping</code> is now accessed through that mapping's new <code>inverseReferenceMapping</code> field. The "opposite property" is specified with the <code>setMappedBy()</code> API.  
 +
 
 +
Back-pointers can also be configured using annotations, as this example shows:
 +
 
 +
<source lang="java5">
 +
public class Employee {
 +
    @XmlElement(name="name")
 +
    private String name;
 +
 +
    @XmlElementWrapper(name="phone-numbers")
 +
    @XmlElement(name="number")
 +
    private List<PhoneNumber> phoneNumbers;
 +
    ...
 +
}
 +
 
 +
public class PhoneNumber {
 +
    @XmlValue
 +
    private String number;
 +
 
 +
    @XmlInverseReference(mappedBy="phoneNumbers")
 +
    private Employee owningEmployee;
 +
    ...
 +
}
 +
</source>
  
 
Several changes were required throughout OXM to support this change, e.g.:  
 
Several changes were required throughout OXM to support this change, e.g.:  
Line 139: Line 184:
 
<source lang="java5">
 
<source lang="java5">
 
...
 
...
if (xmlCompositeCollectionMapping.getBidirectionalPolicy().getBidirectionalTargetContainerPolicy() == null) {
+
if (mapping.getBidirectionalPolicy().getBidirectionalTargetContainerPolicy() == null) {
 
...
 
...
 
</source>  
 
</source>  
Line 147: Line 192:
 
<source lang="java5">
 
<source lang="java5">
 
...
 
...
if (xmlCompositeCollectionMapping.getBackReferenceMapping().getContainerPolicy() == null) {
+
if (mapping.getInverseReferenceMapping().getContainerPolicy() == null) {
 
...
 
...
 
</source>
 
</source>

Latest revision as of 17:00, 4 December 2009



Dynamic Persistence with EclipseLink OXM

This page provides an overview for the work done to support Dynamic Persistence in EclipseLink OXM.

Document History

Date Author Version Description & Notes
091117 Rick Barkhouse 1.0
091125 Rick Barkhouse 1.1 Changed XMLBackReferenceMapping to XMLInverseReferenceMapping
091127 Rick Barkhouse 1.11 Code formatting
091201 Rick Barkhouse 1.2 Added example code showing equivalent annotations


Overview

The first step in creating a JPA-JAXB end-to-end solution is to enable EclipseLink OXM to use Dynamic Persistence, a.k.a. processing Projects, Mappings & Descriptors without having concrete Java classes available. This work builds off of the initial Dynamic Persistence functionality (org.eclipse.persistence.dynamic) current used by the JPA component. By supplying a DynamicClassLoader and loading the EclipseLink project by way of DynamicTypeBuilder.loadDynamicProject(), we can inject Dynamic Persistence functionality into EclipseLink OXM in a fairly transparent way.

Prototype

We currently have a prototype available that can read an EclipseLink project, unmarshal an XML document, modify the Java domain objects (using reflection), and marshal the modified objects, all without having actual Java class files available on the classpath. To get this prototype working the following changes were required:

Instantiating a "Dynamic" Project

Dynamic classes come into play when we use the DynamicTypeBuilder.loadDynamicProject() to load a Project. In this example, even though we have a concrete Project object (DynamicTestProject) which only contains Java class names and no references to concrete Java classes, we will pass this project through to DynamicTypeBuilder to cause the Dynamic classes to be created.

We also pass in an instance of DynamicClassLoader that we create using a system classloader as its parent.

ClassLoader sysClassLoader = Thread.currentThread().getContextClassLoader();
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(sysClassLoader);
 
Project noClassesTestProject = new DynamicTestProject();
Project dynamicTestProject =
   DynamicTypeBuilder.loadDynamicProject(noClassesTestProject , null, dynamicClassLoader);
 
setProject(dynamicTestProject);

We can also load a project.xml file and use it to create our Dynamic project.

DynamicTypeBuilder (org.eclipse.persistence.dynamic)

  • A new method to load a dynamic Project by passing in a "dry" EclipseLink project (one with only class names defined) was added for convenience:
public static Project loadDynamicProject(Project project, 
                                         DatabaseLogin login, 
                                         DynamicClassLoader dynamicClassLoader)
  • A change was needed to avoid a ClassCastException (because we are now potentially dealing with XML mappings):
private boolean requiresInitialization(DatabaseMapping mapping) {
   ...
   if (mapping.isAggregateMapping() && !mapping.isXMLMapping()) {
      return !((AggregateObjectMapping) mapping).isNullAllowed();
   }
   ...
}

OXM Back-Pointer Support

Some changes were needed in the way that back-pointers are configured in OXM. Previously, a back-pointer would be configured on the source descriptor's mapping, e.g.:

...
XMLCompositeCollectionMapping phone = new XMLCompositeCollectionMapping();
phone.setAttributeName("phoneNumbers");
phone.setXPath("phone-numbers/number");
phone.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XPhoneNumber");
...
phone.getBiDirectionalPolicy().setBidirectionalTargetAttributeName("owningEmployee");
phone.getBiDirectionalPolicy().setBidirectionalTargetGetMethodName("getOwningEmployee");
phone.getBiDirectionalPolicy().setBidirectionalTargetSetMethodName("setOwningEmployee");
...

The problem is that when Dynamic Types are being created, class attributes are created for each of the descriptor's mappings; and since the owningEmployee attribute is not mapped on the Phone descriptor, one would not be generated on the dynamic Phone class.

The solution was to create a new type of mapping (tentatively XMLInverseReferenceMapping) that can be set on the target descriptor, and delegate it's functionality back to the original back-pointer code in XMLCompositeObjectMapping:

public class XMLInverseReferenceMapping extends AggregateMapping implements ContainerMapping {
...
@Override
public void postInitialize(AbstractSession session) throws DescriptorException {
   // Get the corresponding mapping from the reference descriptor and set up the 
   // inverse mapping.
   DatabaseMapping mapping =
      getReferenceDescriptor().getMappingForAttributeName(this.mappedBy);
 
   if (mapping instanceof XMLCompositeCollectionMapping) {
      XMLCompositeCollectionMapping oppositeMapping = (XMLCompositeCollectionMapping) mapping;
      oppositeMapping.setInverseReferenceMapping(this);
   }
   ...
}
...

NOTE: In moving the bi-directional aspect from a property on the source descriptor to its own XML Mapping, it no longer seemed appropriate to use "bi-directional" terminology, which is why we chose XMLInverseReferenceMapping. On 091124 in a conference call with Shaun we changed this from the original XMLBackReferenceMapping to XMLInverseReferenceMapping.

With these changes, back-pointers are now configured as follows:

// EMPLOYEE Descriptor
XMLCompositeCollectionMapping phone = new XMLCompositeCollectionMapping();
phone.setAttributeName("phoneNumbers");
phone.setXPath("phone-numbers/number");
phone.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XPhoneNumber");
phone.useCollectionClass(ArrayList.class);
phone.setContainerPolicy(ContainerPolicy.buildPolicyFor(ArrayList.class));
descriptor.addMapping(phone);
 
// PHONE Descriptor
XMLInverseReferenceMapping owningEmployee = new XMLInverseReferenceMapping();
owningEmployee.setReferenceClassName("org.eclipse.persistence.testing.oxm.dynamic.XEmployee");
owningEmployee.setMappedBy("phoneNumbers");
owningEmployee.setAttributeName("owningEmployee");
owningEmployee.setSetMethodName("setOwningEmployee");
owningEmployee.setGetMethodName("getOwningEmployee");
descriptor.addMapping(owningEmployee);

What was previously containerAttributeName/containerGetMethodName/containerSetMethodName on XMLCompositeCollectionMapping is now accessed through that mapping's new inverseReferenceMapping field. The "opposite property" is specified with the setMappedBy() API.

Back-pointers can also be configured using annotations, as this example shows:

public class Employee {
    @XmlElement(name="name")
    private String name;
 
    @XmlElementWrapper(name="phone-numbers")
    @XmlElement(name="number")
    private List<PhoneNumber> phoneNumbers;
    ...
}
 
public class PhoneNumber {
    @XmlValue
    private String number;
 
    @XmlInverseReference(mappedBy="phoneNumbers")
    private Employee owningEmployee;
    ...
}

Several changes were required throughout OXM to support this change, e.g.:

...
if (mapping.getBidirectionalPolicy().getBidirectionalTargetContainerPolicy() == null) {
...

becomes

...
if (mapping.getInverseReferenceMapping().getContainerPolicy() == null) {
...

Back to the top