Unmarshall with JAXB doesn't work - java

I have a simpe XML that I want to unmarshall into a model class. I have annotated the class with JAXB annotations for defining the access type (FIELD):
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
#XmlAccessorType(XmlAccessType.FIELD)
public class DtoTest {
private String name;
public DtoTest() {}
public DtoTest(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "DtoTest [name=" + name + "]";
}
}
This is my main class where I run an unmarshal method against a simple XML saved in a String variable:
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
/* This is a generic unmarshall method which I've already used with success with other XML*/
public static <T> T unmarshal(T obj, String xml) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(xsr, type).getValue();
xsr.close();
return obj;
}
}
Whenever I run the code I get the same output:
DtoTest [name=null]
I don't understand what I'm doing wrong.

I've just run your code on jdk1.7.0_67 and it works.
DtoTest [name=example]
Maybe you have some problem with included libraries? I've run it with just plain java.

What you have in your question runs perfectly fine for me. One optimization you could make to it is to create an StreamSource instead of an XMLStreamReader.
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
public static <T> T unmarshal(T obj, String xml) throws Exception {
StreamSource source = new StreamSource(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(source, type).getValue();
return obj;
}
}
Debugging Tip
When unmarshalling is not working as expected, populate your JAXB model and marshal it to XML to see what the expected XML looks like.

Related

Jaxb String from XML

Using a Jaxb unmarshaller, I cannot achieve to load a XML content as a string.
Here is a running example of what I am trying to achieve.
public static class BarAdapter extends XmlAdapter<Object, String> {
#Override
public Object marshal(String v) throws Exception {
return null;
}
#Override
public String unmarshal(Object v) throws Exception {
return null; // what to do with the ElementNsImpl??
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class Container implements Serializable {
#XmlAnyElement
#XmlJavaTypeAdapter(BarAdapter.class)
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Container.class);
String xml = "<foo><bar><name>Barry</name><surName>White</surName></bar></foo>";
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream is = new ByteArrayInputStream(xml.getBytes());
JAXBElement<Container> barWrapperElement = unmarshaller.unmarshal(new StreamSource(is), Container.class);
Container container = barWrapperElement.getValue();
System.out.println(container.getBar());
}
I would like to have into bar : <bar><name>Barry</name><surName>White</surName></bar>
I've tried to use the #XmlAnyElement but it gives a ElementNsImpl and I need a String.
If you have a better solution, please post. I am feeling that I am not doing it right.
I can transform ElementNsImpl into a String.
So :
#Override
public String unmarshal(Object obj) throws Exception {
// Be careful, affect a new string writer has to be done within your
// unmarshaller. If you do this here, it will partially unmarshall.
// I can povide more code upon request
StringWriter stringWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
Document document = ((Element) obj).getOwnerDocument();
transformer .transform(new DOMSource(document), new StreamResult(stringWriter));
return stringWriter.toString();
}

JAXB - Move dynamically generated namespaces to document root

I've got this POJO, encapsulating a dynamic, non-nested element of an Atom entry:
public class SimpleElement {
private Namespace namespace;
private String tagName;
private String value;
private Collection<Attribute> attributes;
/* getters/setters/... */
And for completeness, Attribute
public class Attribute {
private String name;
private String value;
private Namespace namespace;
/* getters/setters/... */
And Namespace:
public class Namespace {
private final String uri;
private final String prefix;
/* getters/setters/... */
SimpleElementAdapter serializes a SimpleElement into its org.w3c.dom.Element counterpart.
The only problem with this approach is that namespaces always end up at element level, never at document root.
Is there a way to dynamically declare namespaces at document root?
MY RECOMMENDATION
My recommendation is to let the JAXB implementation write the namespace declarations as it sees fit. As long as the elements are properly namespace qualified it does not really matter where the namespace declarations occur.
If you ignore my recommendation, below is an approach you can use.
ORIGINAL ANSWER
Specify the Namespaces to Include on Root Element
You can use the NamespacePrefixMapper extension to add extra namespace declarations to the root element (see: https://jaxb.java.net/nonav/2.2.11/docs/ch05.html#prefixmapper). You will need to derive from your own object model what namespaces should be declared at the root.
Note: NamespacePrefixMapper is in the com.sun.xml.bind.marshaller package. This means you will need the JAXB refereince implementation jar on your classpath (see: https://jaxb.java.net/).
import com.sun.xml.bind.marshaller.*;
public class MyNamespacePrefixMapper extends NamespacePrefixMapper {
#Override
public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
return null;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return new String[] {"ns1", "http://www.example.com/FOO", "ns2", "http://www.example.com/BAR"};
}
}
Specify the NamespacePrefixMapper on the Marshaller
The com.sun.xml.bind.namespacePrefixMapper property is used to specify the NamespacePrefixMapper on the Marshaller.
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());
Demo Code
Java Model (Foo)
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Foo {
private Object object;
#XmlAnyElement
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
Demo
import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.Element;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://www.example.com/FOO", "ns1:foo");
foo.setObject(element);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());
marshaller.marshal(foo, System.out);
}
}
Output
Below is sample output that will be produced:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns1="http://www.example.com/FOO" xmlns:ns2="http://www.example.com/BAR">
<ns1:foo/>
</foo>
UPDATE
Clear answer, thanks. However, I need access to the NSMapper from
SimpleElementAdapter. What do you suggest? The only way I see right
now is making the NSMapper a mutable singleton so that
SimpleElementAdapter can add namespaces if needed.
I forgot about your XmlAdapter.
Java Model
Below is a more complicated iteration of the model, where instead of Foo holding an instance of a DOM element, it holds and instance of Bar that gets adapted into an instance of a DOM element.
Foo
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Foo {
private Bar bar;
#XmlAnyElement
#XmlJavaTypeAdapter(BarAdapter.class)
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
Bar
public class Bar {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
BarAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class BarAdapter extends XmlAdapter<Object, Bar>{
#Override
public Object marshal(Bar bar) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://www.example.com/BAR", "ns:bar");
element.setTextContent(bar.getValue());
return element;
}
#Override
public Bar unmarshal(Object arg0) throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Grab Namespace Declarations
Since your object model does not hold the DOM elements directly you can't traverse it to get the namespace declarations. Instead we could do a marshal to a ContentHandler to collect them. Below are the reasons for marshalling to a ContentHandler:
It gives us an easy event which we can use to collection the namespace declarations.
It doesn't actually produce anything so it is the lightest marshal target we can use.
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);
NsContentHandler
The implementation of ContentHandler will look something like:
import java.util.*;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class NsContentHandler extends DefaultHandler {
private Map<String, String> namespaces = new TreeMap<String, String>();
#Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if(!namespaces.containsKey(prefix)) {
namespaces.put(prefix, uri);
}
}
public Map<String, String> getNamespaces() {
return namespaces;
}
}
Specify the Namespaces to Include on Root Element
The implementation of MyNamespacePrefixMapper changes a little to use the namrespaces captured from our ContentHandler.
import java.util.Map;
import java.util.Map.Entry;
import com.sun.xml.bind.marshaller.*;
public class MyNamespacePrefixMapper extends NamespacePrefixMapper {
private String[] namespaces;
public MyNamespacePrefixMapper(Map<String, String> namespaces) {
this.namespaces = new String[namespaces.size() * 2];
int index = 0;
for(Entry<String, String> entry : namespaces.entrySet()) {
this.namespaces[index++] = entry.getKey();
this.namespaces[index++] = entry.getValue();
}
}
#Override
public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
return null;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return namespaces;
}
}
Demo Code
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Bar bar = new Bar();
bar.setValue("Hello World");
Foo foo = new Foo();
foo.setBar(bar);
Marshaller marshaller = jc.createMarshaller();
// Marshal First Time to Get Namespace Declarations
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);
// Marshal Second Time for Real
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper(contentHandler.getNamespaces()));
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns="http://www.example.com/BAR">
<ns:bar>Hello World</ns:bar>
</foo>

XML file to Specify Java class and return object

I have a Maven & Spring based Java web application
In src/main/resources, I have one XML file.
sourceconfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<sourceConfig area="Defects">
<adapterObject>jAdapter</adapterObject>
<resultObject>jsonObject</resultObject>
</sourceConfig>
In I have a POJO for this SourceConfig.java
#XmlRootElement
public class SourceConfig {
String area;
String adapterObject;
String resultObject;
public String getArea() {
return area;
}
#XmlAttribute
public void setArea(String area) {
this.area = area;
}
public String getAdapterObject() {
return adapterObject;
}
#XmlElement
public void setAdapterObject(String adapterObject) {
this.adapterObject = adapterObject;
}
public String getResultObject() {
return resultObject;
}
#XmlElement
public void setResultObject(String resultObject) {
this.resultObject = resultObject;
}
}
I am able to parse the xml to object.
public class SourceAdapterConfig {
public SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
}
It is working fine.
But all are String. Some I want as object. For example, In XML I have mentioned <resultObject>jsonObject</resultObject>
I have a class com.myapp.config.JsonObject.java
So, instead of <resultObject>jsonObject</resultObject> If I mention class like this
<resultObject class="com.myapp.config.JsonObject">jsonObject</resultObject>
or some other way to mention class, I should be able to get a JsonObject object in my SourceConfig How can I do that?
use java reflection
Class theClass = Class.forName("com.example.Test");
Test testObject = (Test)theClass.newInstance();
This will create an instance of com.example.Test.
In your context,
public class SourceAdapterConfig {
private SourceConfig config;
private SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
public SourceAdapterConfig(){
config = getConfigObject();
}
public Object getAdapterObject(){
String adapterClassName = config.getAdapterObject();
Class theClass = Class.forName(adapterClassName);
return theClass.newInstance();
}
}
Usage:
SourceAdapterConfig config = new SourceAdapterConfig();
Object adapterObject = config.getAdapterObject();

How can I get Gson to use accessors rather than fields?

By default Gson uses fields as a basis for it's serialization. Is there a way to get it to use accessors instead?
The developers of Gson say that they never felt swayed by the requests to add this feature and they were worried about murkying up the api to add support for this.
One way of adding this functionality is by using a TypeAdapter (I apologize for the gnarly code but this demonstrates the principle):
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class AccessorBasedTypeAdaptor<T> extends TypeAdapter<T> {
private Gson gson;
public AccessorBasedTypeAdaptor(Gson gson) {
this.gson = gson;
}
#SuppressWarnings("unchecked")
#Override
public void write(JsonWriter out, T value) throws IOException {
out.beginObject();
for (Method method : value.getClass().getMethods()) {
boolean nonBooleanAccessor = method.getName().startsWith("get");
boolean booleanAccessor = method.getName().startsWith("is");
if ((nonBooleanAccessor || booleanAccessor) && !method.getName().equals("getClass") && method.getParameterTypes().length == 0) {
try {
String name = method.getName().substring(nonBooleanAccessor ? 3 : 2);
name = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name);
Object returnValue = method.invoke(value);
if(returnValue != null) {
TypeToken<?> token = TypeToken.get(returnValue.getClass());
TypeAdapter adapter = gson.getAdapter(token);
out.name(name);
adapter.write(out, returnValue);
}
} catch (Exception e) {
throw new ConfigurationException("problem writing json: ", e);
}
}
}
out.endObject();
}
#Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Only supports writes.");
}
}
You can register this as a normal type adapter for a given type or through a TypeAdapterfactory - possibly checking for the presence of a runtime annotation:
public class TypeFactory implements TypeAdapterFactory {
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
Class<? super T> t = type.getRawType();
if(t.isAnnotationPresent(UseAccessor.class)) {
return (TypeAdapter<T>) new AccessorBasedTypeAdaptor(gson);
}
return null;
}
This can be specified as normal when creating your gson instance:
new GsonBuilder().registerTypeAdapterFactory(new TypeFactory()).create();
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
If you can't get Gson to do what you want, below is how you can accomplish this using MOXy's native JSON binding. MOXy like any JAXB implementation will use property (public) access by default. You can configure field access using #XmlAccessorType(XmlAccessType.FIELD). Below is an example:
Customer
package forum11385214;
public class Customer {
private String foo;
private Address bar;
public String getName() {
return foo;
}
public void setName(String name) {
this.foo = name;
}
public Address getAddress() {
return bar;
}
public void setAddress(Address address) {
this.bar = address;
}
}
Address
package forum11385214;
public class Address {
private String foo;
public String getStreet() {
return foo;
}
public void setStreet(String street) {
this.foo = street;
}
}
jaxb.properties
To configure MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum11385214;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum11385214/input.json");
Customer customer = (Customer) unmarshaller.unmarshal(json, Customer.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
}
}
input.json/Output
{
"name" : "Jane Doe",
"address" : {
"street" : "1 Any Street"
}
}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
http://blog.bdoughan.com/2012/04/jaxb-and-unmapped-properties.html

xml:base in JAXB

I have some XML like this:
<root xml:base="http://www.example.com/foo">
<childElement someAttribute="bar/blort.html"/>
<childElement someAttribute="bar/baz/foo.html"/>
</root>
The schema for my XML defines someAttribute as being of type xs:anyURI
I want to use JAXB to unmarshall the XML into an object model a bit like this:
#XmlRootElement(name="root")
class Root {
#XmlElement(name="childElement")
private List<Child> _children;
}
class Child {
#XmlAttribute(name="someAttribute")
private URI _someAttribute;
}
I would like values of someAttribute to be resolved according to XML base, i.e. when I unmarshall the XML given above, I want the childrens' attributes to be resolved to java.net.URI instances with values http://www.example.com/foo/bar/blort.html and so on.
I was hoping a custom XmlAdapter would allow me to achieve the right result, but the XmlAdapter has no access to the surrounding context, in particular, the value of xml:base in effect at that point (note that this is not as simple as the most recent enclosing value of xml:base as xml:base can appear anywhere in the tree, and relative xml:bases must be resolved against their ancestors).
I'm using EclipseLink's MOXY implementation of JAXB, if it matters.
You can leverage an XMLStreamReader and an XmlAdapter to implement this use case:
UriAdapter
The UriAdapter is both an XmlAdapter for handling the URI property, and a StreamFilter that we will use to detect the xml:base attribute.
package forum9906642;
import java.net.URI;
import javax.xml.bind.annotation.adapters.*;
import javax.xml.stream.*;
public class UriAdapter extends XmlAdapter<String, URI> implements StreamFilter {
private String base = "";
public UriAdapter() {
}
public UriAdapter(String base) {
this.base = base;
}
public URI unmarshal(String string) throws Exception {
return new URI(base + '/' + string);
}
public String marshal(URI uri) throws Exception {
if("".equals(base)) {
return uri.toString();
} else {
URI baseURI = new URI(base);
return baseURI.relativize(uri).toString();
}
}
public boolean accept(XMLStreamReader reader) {
if(reader.isStartElement()) {
String newBase = reader.getAttributeValue("http://www.w3.org/XML/1998/namespace", "base");
if(null != newBase) {
base = newBase;
}
}
return true;
}
}
Demo
The code below demonstrates how to use all the pieces together:
package forum9906642;
import java.io.*;
import javax.xml.bind.*;
import javax.xml.stream.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
UriAdapter uriAdapter = new UriAdapter();
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("src/forum9906642/input.xml"));
xsr = xif.createFilteredReader(xsr, uriAdapter);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(uriAdapter);
Root root = (Root) unmarshaller.unmarshal(xsr);
for(Child child : root.getChildren()) {
System.out.println(child.getSomeAttribute().toString());
}
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(uriAdapter);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Child
package forum9906642;
import java.net.URI;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
#XmlAccessorType(XmlAccessType.FIELD)
class Child {
#XmlAttribute(name="someAttribute")
#XmlJavaTypeAdapter(UriAdapter.class)
private URI _someAttribute;
public URI getSomeAttribute() {
return _someAttribute;
}
public void setSomeAttribute(URI _someAttribute) {
this._someAttribute = _someAttribute;
}
}
Output
http://www.example.com/foo/bar/blort.html
http://www.example.com/foo/bar/baz/foo.html
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<childElement someAttribute="bar/blort.html"/>
<childElement someAttribute="bar/baz/foo.html"/>
</root>
I had a similar problem, but with nested xml:base (because of XInclude), so I ended up having to do this:
public class URIFixingUnmarshaller {
private final JAXBContext jaxb;
public URIFixingUnmarshaller(JAXBContext jaxb) {
this.jaxb = jaxb;
}
public <T> JAXBElement<T> unmarshal(SAXSource in, Class<T> as)
throws JAXBException {
CurrLocation curr = new CurrLocation(in.getXMLReader());
Unmarshaller u = jaxb.createUnmarshaller();
u.setListener(new URIUpdater(curr));
return u.unmarshal(new SAXSource(curr, in.getInputSource()), as);
}
private static class CurrLocation extends XMLFilterImpl {
private Locator curr;
public CurrLocation(XMLReader actual) {
setParent(actual);
}
#Override
public void setDocumentLocator(Locator to) {
super.setDocumentLocator(to);
this.curr = to;
}
String resolve(String uri) {
try {
URL base = new URL(curr.getSystemId());
URL absolute = new URL(base, uri);
return absolute.toString();
} catch (MalformedURLException probablyAlreadyAbsolute) {
return uri;
}
}
}
private static class URIUpdater extends Unmarshaller.Listener {
private final CurrLocation curr;
URIUpdater(CurrLocation curr) {
this.curr = curr;
}
#Override
public void afterUnmarshal(Object target, Object parent) {
if (target instanceof SomethingWithRelativeURI) {
SomethingWithRelativeURI casted = (SomethingWithRelativeURI) target;
casted.setPath(curr.resolve(casted.getPath()));
}
}
}
}

Categories

Resources