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/Release/2.4.0/JAXB RI Extensions/ID Resolver"

(New page: <div style="margin:5px;float:right;border:1px solid #000000;padding:5px">__TOC__</div> = Design Documentation: IDResolver = [http://bugs.eclipse.org/360249 ER 360249] In the current JAX...)
 
 
(57 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>
  
= Design Documentation: IDResolver =
+
= ID Resolver =
  
[http://bugs.eclipse.org/360249 ER 360249]
+
In the current JAXB RI, developed by Sun, there is a series of "proprietary" JAXB extensions which provide advanced functionality outside of the JAXB specification (these extension classes and properties reside in the '''com.sun.xml.bind''' package).
  
In the current JAXB RI, developed by Sun, there are a series of "proprietary" JAXB extensions that are available to provide advanced JAXB functionality outside of the JAXB spec (these extension classes reside in the '''com.sun.xml.bind''' package).
+
The abstract class '''IDResolver''' provided in the Sun JAXB implementation allows users to override the ID/IDREF processing of the JAXB runtime.
 
+
The '''@XmlLocation''' annotation is one of these extensions - it allows the user to specify a property on the JAXB object that will be updated (upon unmarshalling) with that object's XML location information (i.e. the line number, column number, and system ID that points to this object's location in the XML input).
+
 
+
This document will outline the design for an EclipseLink equivalent to this extension.
+
 
+
 
+
= Requirements =
+
 
+
* Deliver an '''@XmlLocation''' annotation in the EclipseLink library that will provide the same functionality as the Sun extension.
+
** Line number
+
** Column number
+
** System ID, if applicable
+
* Have zero impact on memory/performance if the user is not using '''@XmlLocation'''.
+
  
  
 
= Behaviour =
 
= Behaviour =
  
* If an object containing an '''@XmlLocation''' property is unmarshalled, a '''Locator''' object will be created and set on the property, containing the XML location info.
+
If an '''IDResolver''' has been set on the '''Unmarshaller''' (via properties), then the following things must happen during unmarshal:
 
+
* If an object with a populated '''Locator''' is marshalled to XML, the '''Locator''' information will appear in the resultant XML.
+
 
+
* If XML is unmarshalled that contains actual '''Locator''' information (e.g. the example above), that information is not read in like a normal mapping; upon unmarshalling the '''Locator''' property will be set to reflect the current XML location.
+
 
+
* If an '''@XmlLocation''' property is also marked as '''@XmlTransient''', then '''Locator''' information will NOT appear in marshalled XML.
+
 
+
 
+
Not all unmarshal sources will be able to provide XML location information.  For example, unmarshalling from a '''File''' would be able to give you line, column and system ID (filename); system ID is not available when unmarshalling from an '''InputStream'''; unmarshalling from a '''Node''' would give you no XML location information at all.
+
 
+
  
{|{{BMTableStyle}}
+
* The '''IDResolver's''' <tt>startDocument()</tt> method must be called when unmarshalling starts.
|-{{BMTHStyle}}
+
* When an '''ID''' value is encountered during unmarshal, the '''IDResolver's''' <tt>bind</tt> method must be called, to bind the object to the ID.
! Unmarshal Source
+
* When an '''IDREF''' value is encountered during unmarshal, the '''IDResolver's''' <tt>resolve</tt> method must be used to obtain the object for the IDREF.
! Line #
+
* The '''IDResolver's''' <tt>endDocument()</tt> method must be called when unmarshalling completes.
! Column #
+
! System ID
+
|-
+
| java.io.File
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
|-
+
| java.io.InputStream
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Delete.gif]]
+
|-
+
| java.io.Reader
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Delete.gif]]
+
|-
+
| java.net.URL
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
|-
+
| javax.xml.stream.XMLEventReader
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Delete.gif]]
+
|-
+
| javax.xml.stream.XMLStreamReader
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Delete.gif]]
+
|-
+
| org.w3c.dom.Node
+
| [[Image:Delete.gif]]
+
| [[Image:Delete.gif]]
+
| [[Image:Delete.gif]]
+
|-
+
| org.xml.sax.InputSource
+
| [[Image:Ok_green.gif]]
+
| [[Image:Ok_green.gif]]
+
| [[Image:Delete.gif]]
+
|}<br>
+
  
  
 
= Configuration =
 
= Configuration =
  
In order to use '''@XmlLocation''', the user must first have a property on their Java object (either a field or get/set pair) of type
+
The user must extend the '''org.eclipse.persistence.jaxb.IDResolver''' class and implement the following abstract methods (<tt>startDocument</tt> and <tt>endDocument</tt> are optional):
'''org.xml.sax.Locator'''.  The user can then specify that this property should be used to track XML location by using either EclipseLink Annotations or XML Bindings.
+
  
 
+
<div style="width:900px">
== Annotations ==
+
 
+
<div style="width:800px">
+
 
<source lang="java">
 
<source lang="java">
package org.eclipse.persistence.oxm.annotations;
+
public abstract Callable<?> resolve(Object id, Class type) throws SAXException;
  
import static java.lang.annotation.ElementType.FIELD;
+
public abstract Callable<?> resolve(Map<String, Object> id, final Class type) throws SAXException;
import static java.lang.annotation.ElementType.METHOD;
+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
  
import java.lang.annotation.Retention;
+
public abstract void bind(Object id, Object obj) throws SAXException;
import java.lang.annotation.Target;
+
  
@Target({METHOD, FIELD})  
+
public abstract void bind(Map<String, Object> id, Object obj) throws SAXException;
@Retention(RUNTIME)
+
public @interface XmlLocation {}
+
</source>
+
</div>
+
  
 +
public void startDocument(ValidationEventHandler errorHandler) throws SAXException {}
  
== XML Bindings ==
+
public void endDocument() throws SAXException {}
 
+
eclipselink_oxm_2_4.xsd:
+
<div style="width:800px">
+
<source lang="xml">
+
...
+
    <xs:element name="xml-transient" substitutionGroup="java-attribute">
+
        <xs:complexType>
+
            <xs:complexContent>
+
                <xs:extension base="java-attribute">
+
                    <xs:attribute name="xml-location" type="xs:boolean" default="false" />
+
                </xs:extension>
+
            </xs:complexContent>
+
        </xs:complexType>
+
    </xs:element>
+
...
+
    <xs:element name="xml-element" substitutionGroup="java-attribute">
+
        <xs:complexType>
+
            <xs:complexContent>
+
                <xs:extension base="java-attribute">
+
                    ...
+
                    <xs:attribute name="xml-location" type="xs:boolean" default="false" />
+
                </xs:extension>
+
            </xs:complexContent>
+
        </xs:complexType>
+
    </xs:element>
+
...
+
 
</source>
 
</source>
 
</div>
 
</div>
  
 +
The user's '''IDResolver''' class can then be passed to the '''Unmarshaller''' through the <tt>setProperty()</tt> method:
  
== Config Options ==
+
<div style="width:900px">
 
+
The '''@XmlLocation''' feature does not expose any configuration options, it is merely a tagging annotation that indicates the property to be used for tracking XML location information.
+
 
+
 
+
= Examples =
+
 
+
The following examples refer to this XML instance document:
+
 
+
<div style="width:800px">
+
<source lang="xml">
+
<?xml version="1.0" encoding="UTF-8"?>
+
<customer>
+
  <id>1872874</id>
+
  <name>Bob Smith</name>
+
</customer>
+
</source>
+
</div>
+
 
+
 
+
== Example 1 ==
+
 
+
This example shows the most basic use case;  the '''Locator''' field is annotated with '''@XmlLocation''' (or, in XML Bindings, the "xml-element" has its "xml-location" attribute set to "true").
+
 
+
Annotations:
+
<div style="width:800px">
+
 
<source lang="java">
 
<source lang="java">
import javax.xml.bind.annotation.*;
 
 
import org.eclipse.persistence.oxm.annotations.XmlLocation;
 
 
import org.xml.sax.Locator;
 
 
@XmlRootElement
 
public class Customer {
 
 
  public int id;
 
 
  public String name;
 
 
  @XmlLocation
 
  public Locator locator;
 
 
    @Override
 
    public String toString() {
 
        String loc = " noLoc";
 
        if (locator != null) {
 
            loc = " L" + locator.getLineNumber() +
 
                  " C" + locator.getColumnNumber() +
 
                  " " + locator.getSystemId();
 
        }
 
 
        return "Customer(" + name + ")" + loc;
 
    }
 
 
}
 
</source>
 
</div>
 
 
Equivalent XML Bindings:
 
<div style="width:800px">
 
<source lang="xml">
 
 
...
 
...
    <java-types>
+
JAXBContext ctx = ...
        <java-type name="Customer">
+
Unmarshaller u = ctx.createUnmarshaller();
            <xml-root-element />
+
u.setProperty(UnmarshallerProperties.ID_RESOLVER, new MyIDResolver());
            <java-attributes>
+
                <xml-element java-attribute="id" />
+
                <xml-element java-attribute="name" />
+
                <xml-element java-attribute="locator" xml-location="true" />
+
            </java-attributes>
+
        </java-type>
+
    </java-types>
+
 
...
 
...
 
</source>
 
</source>
 
</div>
 
</div>
  
When a '''Customer''' is unmarshalled, the '''Locator''' field is automatically set to contain the XML location information for that object.  By default, if that object was then marshalled back to XML, the XML location information would be written out as well.
+
'''Note:''' EclipseLink also supports Sun's IDResolver property names:
  
Unmarshalling and Marshalling:
+
<div style="width:900px">
<div style="width:800px">
+
 
<source lang="java">
 
<source lang="java">
File f = new File("D:/temp/instance.xml"));
+
m.setProperty("com.sun.xml.bind.IDResolver", new MyResolver());
Customer c = jaxbContext.createUnmarshaller().unmarshal(f);
+
m.setProperty("com.sun.xml.internal.bind.IDResolver", new MyResolver());
 
+
System.out.println(c);
+
 
+
  // Output:
+
  // Customer(Bob Smith) L15 C35 file:/D:/temp/instance.xml
+
 
+
jaxbContext.createMarshaller().marshal(c, System.out);
+
 
+
  // Output:
+
  // <?xml version="1.0" encoding="UTF-8"?>
+
  // <customer>
+
  //  <id>1872874</id>
+
  //  <name>Bob Smith</name>
+
  //  <locator>
+
  //      <columnNumber>35</columnNumber>
+
  //      <lineNumber>15</lineNumber>
+
  //      <systemId>file:/D:/temp/instance.xml</systemId>
+
  //  </locator>
+
  // </customer>
+
 
</source>
 
</source>
 
</div>
 
</div>
  
 +
Also note that if you are using a Sun IDResolver with EclipseLink, it will be unable to support EclipseLink's multiple XML IDs feature.  In this case, you should re-implement your IDResolver as a subclass of '''org.eclipse.persistence.jaxb.IDResolver'''.
  
== Example 2 ==
 
  
In most cases, you would not want XML location information written out during marshal, as this information reflects the location the object was unmarshalled FROM, not the location it is being marshalled TO.  If XML location is encountered when unmarshalling XML, it would be ignored anyway, and instead the "fresh" XML location information would be used instead.
+
= Appendix A - Example IDResolver =
  
To avoid writing out XML location during marshal operations, you can additionally annotate your '''@XmlLocation''' property with '''@XmlTransient''' (or, in XML Bindings, use "xml-transient" instead of "xml-element"):
+
<div style="width:925px">
 
+
Annotations:
+
<div style="width:800px">
+
 
<source lang="java">
 
<source lang="java">
import javax.xml.bind.annotation.*;
+
import java.util.LinkedHashMap;
 +
import java.util.Map;
 +
import java.util.concurrent.Callable;
  
import org.eclipse.persistence.oxm.annotations.XmlLocation;
+
import javax.xml.bind.ValidationEventHandler;
  
import org.xml.sax.Locator;
+
import org.eclipse.persistence.jaxb.IDResolver;
  
@XmlRootElement
+
import org.xml.sax.SAXException;
public class Customer {
+
  
  public int id;
+
public class MyIDResolver extends IDResolver {
 +
  Map<Map<String, Object>, Apple> apples = new LinkedHashMap();
 +
  Map<Map<String, Object>, Orange> oranges = new LinkedHashMap();
  
   public String name;
+
  @Override
 +
   public void startDocument(ValidationEventHandler eventHandler) throws SAXException {
 +
      apples.clear();
 +
      oranges.clear();
 +
  }
  
   @XmlLocation
+
   @Override
   @XmlTransient
+
   public void endDocument() throws SAXException {
   public Locator locator;
+
   }
  
   ...
+
   @Override
 +
  public void bind(Map<String, Object> idWrapper, Object obj) throws SAXException {
 +
      if (obj instanceof Apple) {
 +
          ((Apple) obj).processed = true;
 +
          apples.put(idWrapper, (Apple) obj);
 +
      } else {
 +
          ((Orange) obj).processed = true;
 +
          oranges.put(idWrapper, (Orange) obj);
 +
      }
 +
  }
  
}
+
  @Override
</source>
+
  public Callable<Object> resolve(final Map<String, Object> idWrapper, final Class type) throws SAXException {
</div>
+
      return new Callable<Object>() {
 
+
          public Object call() {
Equivalent XML Bindings:
+
              if (type == Apple.class) {
<div style="width:800px">
+
                  return apples.get(idWrapper);
<source lang="xml">
+
              } else {
...
+
                  return oranges.get(idWrapper);
    <java-types>
+
              }
        <java-type name="Customer">
+
          }
            <xml-root-element />
+
      };
            <java-attributes>
+
   }
                <xml-element java-attribute="id" />
+
                <xml-element java-attribute="name" />
+
                <xml-transient java-attribute="locator" xml-location="true" />
+
            </java-attributes>
+
        </java-type>
+
    </java-types>
+
...
+
</source>
+
</div>
+
 
+
Unmarshalling and Marshalling:
+
<div style="width:800px">
+
<source lang="java">
+
File f = new File("D:/temp/instance.xml"));
+
Customer c = jaxbContext.createUnmarshaller().unmarshal(f);
+
 
+
System.out.println(c);
+
 
+
  // Output:
+
  // Customer(Bob Smith) L15 C35 file:/D:/temp/instance.xml
+
 
+
jaxbContext.createMarshaller().marshal(c, System.out);
+
 
+
   // Output:
+
  // <?xml version="1.0" encoding="UTF-8"?>
+
  // <customer>
+
  //  <id>1872874</id>
+
  //  <name>Bob Smith</name>
+
  // </customer>
+
</source>
+
</div>
+
 
+
 
+
== Example 3 ==
+
 
+
Accessor methods can be annotated instead of the actual Java field:
+
 
+
<div style="width:800px">
+
<source lang="java">
+
import javax.xml.bind.annotation.*;
+
 
+
import org.eclipse.persistence.oxm.annotations.XmlLocation;
+
 
+
import org.xml.sax.Locator;
+
 
+
@XmlRootElement
+
public class Customer {
+
 
+
  private int id;
+
 
+
  private String name;
+
 
+
  private Locator locator;
+
  
   @XmlLocation
+
   @Override
   public Locator getLocator() {
+
   public void bind(Object id, Object obj) throws SAXException {
      return this.locator;
+
      Map<String, Object> idMap = new LinkedHashMap<String, Object>(1);
 +
      idMap.put("stringId", id);
 +
      bind(idMap, obj);
 
   }
 
   }
  
   public void setLocator(Locator l) {
+
  @Override
      this.locator = l;
+
   public Callable<?> resolve(Object id, Class type) throws SAXException {
 +
      Map<String, Object> idMap = new LinkedHashMap<String, Object>(1);
 +
      idMap.put("stringId", id);
 +
      return resolve(idMap, type);
 
   }
 
   }
 
  ...
 
  
 
}
 
}
 
</source>
 
</source>
 
</div>
 
</div>
 
 
= Design =
 
 
When processing XML through our various parsing mechanisms (UnmarshalRecord, SAXDocumentBuilder, XMLStreamReaderReader, etc), a Locator object is supplied by the underlying parser libraries.  This Locator is constantly updated throughout the unmarshalling process (with the location of the currently parsed node).  When parsing a startElement(), if that element equates to a Descriptor that is location-aware, the Locator will be cloned at that point, and stored on the UnmarshalRecord, indicating the XML Location of that object in XML.
 
 
 
* New annotation: '''org.eclipse.persistence.oxm.annotations.XmlLocation'''
 
* New boolean "'''xml-location'''" flag on "xml-transient" and "xml-element" in XML Bindings
 
** Must be added to new 2.4 schema - eclipselink_oxm_2_4.xsd
 
** Corresponding updates to o.e.p.jaxb.xmlmodel classes
 
<br>
 
* New field on XMLDescriptor: '''boolean isLocationAware'''
 
<br>
 
* New field on UnmarshalRecord: '''Locator xmlLocation''' (each object instance has its own UnmarshalRecord, and this will be set only if Descriptor isLocationAware)
 
<br>
 
* New field on Property: '''boolean isXmlLocation'''
 
** Property is set up for isXmlLocation in AnnotationsProcessor and XMLProcessor
 
<br>
 
* MappingsGenerator: in generateMapping(), create a specialized XMLCompositeObjectMapping for the Locator property
 
* New class: '''NullInstantiationPolicy''' - we want to create a regular mapping for the Locator, but will never be instantiating it like a regular mapping - its value will be set manually during parsing.  Plus, Locator does not have a default constructor.  This InstantiationPolicy simply returns null for buildNewInstance().
 
<br>
 
* New constants in XMLConstants: '''LOCATOR_CLASS''' and '''LOCATOR_CLASS_NAME'''
 
<br>
 
* New JAXBException ("XmlLocation is only allowed on properties of type org.xml.sax.Locator, but [{0}] is of type [{1}].")
 
 
 
= Document History =
 
{|{{BMTableStyle}}
 
|-{{BMTHStyle}}
 
! Date
 
! Author
 
! Version Description & Notes
 
|-
 
| 111007
 
| Rick Barkhouse
 
| 1.00 : First draft
 
|}<br>
 

Latest revision as of 11:36, 18 June 2012

ID Resolver

In the current JAXB RI, developed by Sun, there is a series of "proprietary" JAXB extensions which provide advanced functionality outside of the JAXB specification (these extension classes and properties reside in the com.sun.xml.bind package).

The abstract class IDResolver provided in the Sun JAXB implementation allows users to override the ID/IDREF processing of the JAXB runtime.


Behaviour

If an IDResolver has been set on the Unmarshaller (via properties), then the following things must happen during unmarshal:

  • The IDResolver's startDocument() method must be called when unmarshalling starts.
  • When an ID value is encountered during unmarshal, the IDResolver's bind method must be called, to bind the object to the ID.
  • When an IDREF value is encountered during unmarshal, the IDResolver's resolve method must be used to obtain the object for the IDREF.
  • The IDResolver's endDocument() method must be called when unmarshalling completes.


Configuration

The user must extend the org.eclipse.persistence.jaxb.IDResolver class and implement the following abstract methods (startDocument and endDocument are optional):

public abstract Callable<?> resolve(Object id, Class type) throws SAXException;
 
public abstract Callable<?> resolve(Map<String, Object> id, final Class type) throws SAXException;
 
public abstract void bind(Object id, Object obj) throws SAXException;
 
public abstract void bind(Map<String, Object> id, Object obj) throws SAXException;
 
public void startDocument(ValidationEventHandler errorHandler) throws SAXException {}
 
public void endDocument() throws SAXException {}

The user's IDResolver class can then be passed to the Unmarshaller through the setProperty() method:

...
JAXBContext ctx = ...
Unmarshaller u = ctx.createUnmarshaller();
u.setProperty(UnmarshallerProperties.ID_RESOLVER, new MyIDResolver());
...

Note: EclipseLink also supports Sun's IDResolver property names:

m.setProperty("com.sun.xml.bind.IDResolver", new MyResolver());
m.setProperty("com.sun.xml.internal.bind.IDResolver", new MyResolver());

Also note that if you are using a Sun IDResolver with EclipseLink, it will be unable to support EclipseLink's multiple XML IDs feature. In this case, you should re-implement your IDResolver as a subclass of org.eclipse.persistence.jaxb.IDResolver.


Appendix A - Example IDResolver

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
 
import javax.xml.bind.ValidationEventHandler;
 
import org.eclipse.persistence.jaxb.IDResolver;
 
import org.xml.sax.SAXException;
 
public class MyIDResolver extends IDResolver {
   Map<Map<String, Object>, Apple> apples = new LinkedHashMap();
   Map<Map<String, Object>, Orange> oranges = new LinkedHashMap();
 
   @Override
   public void startDocument(ValidationEventHandler eventHandler) throws SAXException {
       apples.clear();
       oranges.clear();
   }
 
   @Override
   public void endDocument() throws SAXException {
   }
 
   @Override
   public void bind(Map<String, Object> idWrapper, Object obj) throws SAXException {
       if (obj instanceof Apple) {
           ((Apple) obj).processed = true;
           apples.put(idWrapper, (Apple) obj);
       } else {
           ((Orange) obj).processed = true;
           oranges.put(idWrapper, (Orange) obj);
       }
   }
 
   @Override
   public Callable<Object> resolve(final Map<String, Object> idWrapper, final Class type) throws SAXException {
       return new Callable<Object>() {
           public Object call() {
               if (type == Apple.class) {
                   return apples.get(idWrapper);
               } else {
                   return oranges.get(idWrapper);
               }
           }
       };
   }
 
   @Override
   public void bind(Object id, Object obj) throws SAXException {
       Map<String, Object> idMap = new LinkedHashMap<String, Object>(1);
       idMap.put("stringId", id);
       bind(idMap, obj);
   }
 
   @Override
   public Callable<?> resolve(Object id, Class type) throws SAXException {
       Map<String, Object> idMap = new LinkedHashMap<String, Object>(1);
       idMap.put("stringId", id);
       return resolve(idMap, type);
   }
 
}

Back to the top