Tom
Tom

Reputation: 7740

Jaxb XML namespaces incorrect when using generics and base classes

I have four classes in two namespaces, two in one package are used as base classes to the two in the other, for example:

@XmlAccessorType(XmlAccessType.FIELD)
public class Foo<T extends Bar> {

    @XmlElement(name="Bar")
    private List<T> bar;

}

And

@XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
    // something
}

The derived classes look like:

@XmlType(name="Foo" namespace="urn:foo/Bar")
@XmlRootElement(name="Foo")
public class DerivedFoo extends Foo<DerivedBar> {
    // something
}

And

@XmlType(name="Bar" namespace="urn:foo/Bar")
public class DerivedBar extends Bar {
    // something
}

In the package which contains the base classes, there is no package-info.java or anything containing namespace information. The package containing the derived classes has all of the namespace information.

The output XML is properly (sort of) namespacing the DerivedFoo class but is assigning the DerivedBar class to an unnamed namespace, essentially:

<Response xmlns:ns2="urn:foo/Bar">
  <ns2:Foo>
    <Bar>
    </Bar>
  </ns2:Foo>
</Response>

Upvotes: 1

Views: 429

Answers (1)

Greg Haskins
Greg Haskins

Reputation: 6794

I think what you're looking for is a combination of @XmlElementRef and @XmlRootElement. Using the @XmlElementRef annotation is like saying "here's a thing; go look at its @XmlRootElement annotation to figure out how to serialize it."

The following setup seems to do what you're looking for. Base classes:

package base;

@XmlAccessorType(XmlAccessType.FIELD)
public class Foo<T extends Bar> {

    @XmlElementRef
    public List<T> bar;

}
package base;

@XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
    // something
}
package base;

@XmlRootElement(name = "Request")
@XmlAccessorType(XmlAccessType.FIELD)
public class Request {

    @XmlElementRef
    public Foo<?> foo;
}

And subclasses:

package derived;

@XmlRootElement(name = "Foo", namespace = "urn:foo/Bar")
public class DerivedFoo extends Foo<DerivedBar> {
    // something
}
package derived;

@XmlRootElement(name = "Bar", namespace = "urn:foo/Bar")
public class DerivedBar extends Bar {
    // something
}

Looks pretty good from my test:

@Test
public void tryIt() throws Exception {
    JAXBContext context = JAXBContext.newInstance(Request.class, DerivedFoo.class, DerivedBar.class);

    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    Request request = new Request();
    DerivedFoo foo = new DerivedFoo();
    request.foo = foo;
    foo.bar = new ArrayList<DerivedBar>();
    foo.bar.add(new DerivedBar());

    marshaller.marshal(request, System.out);
}

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Request xmlns:ns2="urn:foo/Bar">
    <ns2:Foo>
        <ns2:Bar/>
    </ns2:Foo>
</Request>

Bonus

Since @XmlElementRef looks up the XML bindings dynamically at runtime, it will "just work" if you declare another subclass of Bar with a different namespace. Be careful though, because the element name is also dynamic, which might lead to weird results if your derived classes don't all use the same name attribute:

<Request xmlns:ns2="urn:rock/roll" xmlns:ns3="urn:yummy">
    <ns2:FooFighters>
        <ns3:TikiBar/>
    </ns2:FooFighters>
</Request>

The nested types probably don't care too much, but I bet whoever is consuming Request would probably not recognize those element names.

Upvotes: 2

Related Questions