4

I am having an issue with using inheritance and JAXB unmarshalling. I have read the number of examples (specifically a much-reference blog at http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html and a very similar SO question here: JAXB xsi:type subclass unmarshalling not working) and am still having difficulties.

Like many of these other questions, I am trying to create an XML representation of an object whose fields rely on subclasses for information. I do not know at compile time what the concrete subclass implementation will be, so things like XmlSeeAlso is not really available.

In my test case I have a Root class with an abstract class (A) that has a concrete subtype (B):

@XmlRootElement
@ToString
    public class Root {

    private A theClass;

    public A getTheClass() {
        return theClass;
    }

    public void setTheClass(A theClass) {
        this.theClass = theClass;
    }
}

@ToString
public abstract class A {

}

@XmlRootElement
@ToString
public class B extends A {
    private  String b = "from B-" + System.currentTimeMillis()
    public String getB() {
       return b;
    }

    public void setB(String b) {
         this.b = b;
    }
}

Where @ToString are annotations from project Lombok.

I can marshall with no problem:

@Test
public void marshallTest() {

    try {
        Root r = new Root();
        B b = new B();

        r.setTheClass(b);
        Class<?>[] classArray = new Class<?> [] { Root.class, B.class };

        JAXBContext context = JAXBContext.newInstance(classArray);
        Marshaller marshaller = context.createMarshaller();

        try (FileWriter fw = new FileWriter(JAXB_TEST_XML)) {
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(r, fw);
        }

        try(StringWriter sw = new StringWriter() ) {
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(r, sw);
            log.debug("{}", sw.toString());
        }

    } catch (IOException | JAXBException e) {
        e.printStackTrace();
        fail(e.getMessage());
    }
}

Which produces the following xml:

<root>
   <theClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b">
      <b>from B-1375211003868</b>
   </theClass>
</root>

When I try to unmarshall (using MOXY JAXB implemntation) I get:

This class does not define a public default constructor, or the constructor raised an exception.
Internal Exception: java.lang.InstantiationException
Descriptor: XMLDescriptor(xml.A --> [])

With the following code:

@Test
public void unmarshall() {

    try {
        JAXBContext context = JAXBContext.newInstance(new Class<?>[] {Root.class, A.class});
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder db = documentBuilderFactory.newDocumentBuilder();

        Root r = null;
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(JAXB_TEST_XML))) {
            Document doc = db.parse(bis);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            JAXBElement<?> result = unmarshaller.unmarshal(doc, Root.class);
            r = (Root) result.getValue();
        }

        assertTrue(r != null & r.getTheClass() != null && r.getTheClass() instanceof B);
    } catch (Exception e) {
        e.printStackTrace();
        fail(e.getMessage());
    }
}

I have tried making the unmarshalling namespace aware (as with JAXB xsi:type subclass unmarshalling not working which has not worked. I have tried using XmlElementRef which does not work either. I have tried the latest glassfish api and implementations downloaded from maven central (2.2.8). I have tried the MOXY eclipse persistence JAXB implementation. Neither have worked. I have tried unmarshalling without using a document builder which does not work as well. I have tried passing the Root.class and A.class into the JAXB context which also does not work.

I have a feeling that I have a fundamental misconception about what is going on. Any hints or ideas would be appreciated. Thank you.

  • Chooks
Community
  • 1
  • 1
chooks
  • 694
  • 7
  • 20

1 Answers1

1

UPDATE 2

You could use a library to determine the subclasses dynamically and pass that result to MOXy to build the JAXBContext. Below is an enhancement request that was entered suggesting Jandex for this purpose.


UPDATE 1

There was an issue prior to EclipseLink 1.2.0 (released October 23, 2009) where that exception could be thrown even though everything was setup correctly.


You just need to make the JAXBContext aware of the B class. One way to do this is to leverage the @XmlSeeAlso annotation on the A class.

import javax.xml.bind.annotation.XmlSeeAlso;

@XmlSeeAlso({B.class})
public abstract class A {

}

You can also include B in the classes used to bootstrap the JAXBContext:

JAXBContext jc = JAXBContext.newInstance(Root.class, B.class);
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • I want to avoid putting the concrete class reference in at compile time (see second paragraph in question). I am using moxy 2.5.0 from maven central (group id of org.eclipse.persistence and artifact Id of org.eclipse.persistence.moxy). I thought that the xsi:type specification would allow the JAXBContext to look for classes that match that type. If that is not the case then I may need to rethink what I am doing and use a different approach other than XML. But for the record - yes -- explicitly telling the JAXBContext about the concrete class makes things work. – chooks Jul 30 '13 at 19:58
  • @chooks - I have updated my answer. Potentially you could use another library to determine the classes, and pass the result of that to MOXy to create the `JAXBContext`. – bdoughan Jul 30 '13 at 20:14
  • 1
    Thanks for the pointer to jandex. I had thought about trying to do something similar, but thought that I was probably doing something wrong with my unmarshalling and xsi:type. Thanks for taking the time to address my question. – chooks Jul 31 '13 at 13:16
  • I have the same problem and nothing works. I'm using jaxb-ri-2.1-2 (which is what Maven picked for me) and am schema driven. xjc has declared the abstract class as @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "BaseProgramGroupTypeType") @XmlSeeAlso({ ProgramGroupTypeType.class }) public abstract class BaseProgramGroupTypeType { } – Julian Sep 20 '13 at 10:34