August 16, 2011

Using an Unmarshaller.Listener to Capture the Location

Recently I came across a question on Stack Overflow asking how find where in the document an object was unmarshalled from. In this post I'll expand on my answer of using an XMLStreamReader and Unmarshaller.Listener to accomplish this.


LocationListener

The Unmarshaller.Listener is registered on an Unmarshaller and provides an opportunity to receive events during the unmarshal operation.  The events are beforeUnmarshal (when the object is instantiated, but before it is populated), and afterUnmarshal (after the object is populated).  In this example we will leverage the beforeUnmarshal event to record the location from the XMLStreamReader when the object is instantiated.

package blog.unmarshaller.listener;

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.Unmarshaller.Listener;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamReader;

class LocationListener extends Listener {

    private XMLStreamReader xsr;
    private Map<Object, Location> locations;

    public LocationListener(XMLStreamReader xsr) {
        this.xsr = xsr;
        this.locations = new HashMap<Object, Location>();
    }

    @Override
    public void beforeUnmarshal(Object target, Object parent) {
        locations.put(target, xsr.getLocation());
    }

    public Location getLocation(Object o) {
        return locations.get(o);
    }

}

Demo

The LocationListener holds a reference to the XMLStreamReader we are unmarshalling from.  We set in on the Unmarshaller so that we can receive events during the unmarshal operation.  The LocationListener maintains a map of Object to Location instances, that we can use to look up the locations of the unmarshalled objects.

package blog.unmarshaller.listener;

import java.io.FileInputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        XMLInputFactory xif = XMLInputFactory.newFactory();
        FileInputStream xml = new FileInputStream("src/blog/unmarshaller/listener/input.xml");
        XMLStreamReader xsr = xif.createXMLStreamReader(xml);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        LocationListener ll = new LocationListener(xsr);
        unmarshaller.setListener(ll);

        Customer customer = (Customer) unmarshaller.unmarshal(xsr);
        System.out.println(ll.getLocation(customer));
        System.out.println(ll.getLocation(customer.getAddress()));
    }

}

input.xml

Below is the XML that will be unmarshalled:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name>Jane Doe</name>
    <address>
        <street>123 A Street</street>
    </address>
</customer>

Output

Below is the output from running the example:

[row,col {unknown-source}]: [2,1]
[row,col {unknown-source}]: [4,5]

Java Model

The following domain model was used for this example:

Customer

package blog.unmarshaller.listener;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {

    private String name;
    private Address address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

Address

package blog.unmarshaller.listener;

public class Address {

    private String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

}

Further Reading

For more information on JAXB events refer to the EclipseLink JAXB (MOXy) User Guide:

1 comment:

  1. Thank you for the great explenation and demo code it helps me a lot. But I have one problem how I get the line number of the name or street element? I did a lot of tests but I don't find a solution.

    ReplyDelete