EclipseLink/Release/2.4.0/JAXB RI Extensions/XML Location

From Eclipsepedia

Jump to: navigation, search

Design Documentation: @XmlLocation

ER 355766

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 @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

  • 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 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.


Unmarshal Source Line # Column # System ID
java.io.File Ok green.gif Ok green.gif Ok green.gif
java.io.InputStream Ok green.gif Ok green.gif Delete.gif
java.io.Reader Ok green.gif Ok green.gif Delete.gif
java.net.URL Ok green.gif Ok green.gif Ok green.gif
javax.xml.stream.XMLEventReader Ok green.gif Ok green.gif Delete.gif
javax.xml.stream.XMLStreamReader Ok green.gif Ok green.gif Delete.gif
org.w3c.dom.Node Delete.gif Delete.gif Delete.gif
org.xml.sax.InputSource Ok green.gif Ok green.gif Delete.gif


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 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.


Annotations

package org.eclipse.persistence.oxm.annotations;
 
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
 
@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface XmlLocation {}


XML Bindings

eclipselink_oxm_2_4.xsd:

...
    <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>
...


Config Options

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:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <id>1872874</id>
   <name>Bob Smith</name>
</customer>


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:

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;
    }
 
}

Equivalent XML Bindings:

...
    <java-types>
        <java-type name="Customer">
            <xml-root-element />
            <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>
...

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.

Unmarshalling and Marshalling:

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>
   //   <locator>
   //      <columnNumber>35</columnNumber>
   //      <lineNumber>15</lineNumber>
   //      <systemId>file:/D:/temp/instance.xml</systemId>
   //   </locator>
   // </customer>


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.

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"):

Annotations:

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
   @XmlTransient
   public Locator locator;
 
   ...
 
}

Equivalent XML Bindings:

...
    <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>
...

Unmarshalling and Marshalling:

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>


Example 3

Accessor methods can be annotated instead of the actual Java field:

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
   public Locator getLocator() {
      return this.locator;
   }
 
   public void setLocator(Locator l) {
      this.locator = l;
   }
 
   ...
 
}


Design

When processing XML through our various parsing mechanisms (UnmarshalRecord, SAXDocumentBuilder, 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 set on the Descriptor, 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


  • New field on XMLDescriptor: boolean isLocationAware
  • New field on XMLDescriptor: Locator xmlLocation (only set if isLocationAware)


  • New field on Property: boolean isXmlLocation
    • Property is set up for isXmlLocation in AnnotationsProcessor and XMLProcessor


  • 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().


  • New constants in XMLConstants: LOCATOR_CLASS and LOCATOR_CLASS_NAME


  • New JAXBException ("XmlLocation is only allowed on properties of type org.xml.sax.Locator, but [{0}] is of type [{1}].")


Document History

Date Author Version Description & Notes
110926 Rick Barkhouse 1.00 : First draft
110927 Rick Barkhouse 1.01 : Added design information