Reusing object in two different namespaces with JAXB

Viewed 2455

I'm in a situation where I need to create and parse a set of xml's that are quite similar in structure but unfortunately have been defined in several different namespaces. I'm generally able to parse and create all the XML's using JAXB, but seems to have some problems getting JAXB to handle the namespace definitions. The following simple example should illustrate the problem.

If I define an annotated class with a namespace like this:

Bus.java:

@XmlRootElement(name = "Bus", namespace = "http://www.example.org/bus")
@XmlAccessorType(XmlAccessType.FIELD)
public class Bus {
    @XmlElement(name = "Driver")
    Driver driver;
}

And define another class used by this class without specifying a namespace

Driver.java:

@XmlAccessorType(XmlAccessType.FIELD)
public class Driver {
    @XmlElement(name = "DriverName")
    String driverName;
}

And at the same time I have the following package level definition that allows me to use the Driver class in the namespace of Bus without explicitly defining it's namespace in the class:

package-info.java

@XmlSchema(namespace = "http://www.example.org/bus", 
  xmlns = { @XmlNs(prefix = "", namespaceURI = "http://www.example.org/bus") }
, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)

And I have an xml like this:

String xml = "<Bus xmlns=\"http://www.example.org/bus\">" + 
             "  <Driver>" +
             "    <DriverName>Bob</DriverName>" +
             "  </Driver>" + 
             "</Bus>";

Then I can simply parse / recreate the xml using something like this:

JAXBContext context = JAXBContext.newInstance(Bus.class);

// parse xml
Unmarshaller um = context.createUnmarshaller();
Object xmlObj = um.unmarshal(new StringReader(xml));

// recreate xml 
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
marshaller.marshal(xmlObj, sw);
String recreatedXml = sw.toString();

System.out.println("recreatedXml);

That gives:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Bus xmlns="http://www.example.org/bus">
    <Driver>
        <DriverName>Bob</DriverName>
    </Driver>
</Bus>

But how do I go about reusing the Driver class in another namespace e.g. like this ?

Car.java

@XmlRootElement(name = "Car", namespace = "http://www.example.org/car")
@XmlAccessorType(XmlAccessType.FIELD)
public class Car {
    @XmlElement(name = "Driver")
    Driver driver;
}

package-info.java only allows for one XmlSchema, and without it, each field seems to need an explicit namespace.

2 Answers

Abstract idea

Looking through xml lens Driver from http://www.example.org/bus is different from Driver from http://www.example.org/car. You cant simply reuse them, that is your problem.

You are using JAXB which allows you to establish maping between Java classes and xml elements. This enables you looking on the same problem from Java classes perspective.

Now, when talking about code reuse in Java (and OOP in generally) one of the most popular ways of code reuse is inheritance.

So, the core idea is to use inheritance to share "code" (in your case simply property definitions) between Drivers from different namespaces.

Implementation

I'd recommend structuring your classes this way:

└───xml
    │   DriverBase.java
    │
    ├───bus
    │       Bus.java
    │       Driver.java
    │       package-info.java
    │
    └───car
            Car.java
            Driver.java
            package-info.java

... where xml.DriverBase is the base for your xml.bus.Driver and xml.car.Driver and looks like:

package xml;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

@XmlTransient
@XmlAccessorType(XmlAccessType.PROPERTY)
abstract public class DriverBase {
    @XmlElement(name = "DriverName")
    String driverName;
}

Note the use of @XmlTransient, since we don't actually want JAXB to map DriverBase, only its child classes.

xml.bus.Driver simply inherits xml.DriverBase and should look like this:

package xml.bus;

import xml.DriverBase;

public class Driver extends DriverBase {
}

xml.bus.Bus is roughly the same as before:

package xml.bus;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Bus", namespace = "http://www.example.org/bus")
@XmlAccessorType(XmlAccessType.FIELD)
public class Bus {
    @XmlElement(name = "Driver")
    Driver driver;
}

... as well as package-info.java for xml.bus:

@javax.xml.bind.annotation.XmlSchema(
        namespace = "http://www.example.org/bus",
        xmlns = { @javax.xml.bind.annotation.XmlNs(prefix = "", namespaceURI = "http://www.example.org/bus") },
        elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED
)
package xml.bus;

xml.car.* classes are analogous to xml.bus.* classes.

This should work for you.

You have write another package and package-info where you should define xml-schema for Car. And you can easily used common classes from that package.

From same package, you can't change namespace as you expect.

Related