2

I have a RESTful Jersey web service that returns objects as XML. One of the members of the returned object is one of several possible types (all derived from a common base class) which isn't known until runtime. I can't get this member to appear in the output XML, as I will show below.

I've reduced my problem to some sample classes:

@XmlRootElement
public abstract class Animal {

    public String type;

    public Animal() {
        type = "?";
    }

    public Animal(String type) {
        this.type = type;
    }
}

@XmlRootElement
public class Mammal extends Animal {

    public String name;

    public Mammal() {
        name = "?";
    }

    public Mammal(String type, String name) {
        super(type);
        this.name = name;
    }
}

@XmlRootElement
public class Zoo {
    private Animal creature;

    @XmlElementRef
    public Animal getCreature() {
        return creature;
    }

    public void setCreature(Animal creature) {
        this.creature = creature;
    }

    public Zoo() {
        creature = new Mammal("Mouse", "Mickey");
    }
}

@Path("/test")
public class TestResource {

    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Zoo get() {
        Zoo z = new Zoo();
        return z;
    }
}

When I call the service I get the following response:

<zoo/>

Which doesn't include the expected creature.

If I change the type of the member 'creature' from an Animal (abstract base class) to a Mammal (derived class) then I get the expected output, but that's not a suitable solution.

So I added the following code to the get() method:

    // DIAGNOSTIC CODE: Convert object to XML and send to System.out
    try {
        JAXBContext context = JAXBContext.newInstance(Zoo.class, Mammal.class);
        Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        m.marshal(z, System.out);
    } catch (JAXBException ex) {
        ex.printStackTrace();
    }

and now even though the XML from the service is wrong (just the empty tag) I see the correct output in the console :

<?xml version="1.0" encoding="UTF-8"?>
<zoo>
   <mammal>
      <type>Mouse</type>
      <name>Mickey</name>
   </mammal>
</zoo>

Now, In order to get that output, I had to make sure that Mammal.class was passed to JAXBContext.newInstance(), otherwise I got the same empty tag in the output, so I'm guessing the problem is that the XML serialization being done by the web service doesn't know about the derived Mammal class, and thus can't serialize the object correctly. Is that a correct diagnosis? How do I fix it?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Dave W
  • 1,061
  • 3
  • 16
  • 29

1 Answers1

2

The problem is that the default JAXBContext that Jersey will create from the return type Zoo will not be aware of the Mammal class. It will pull in Animal, but not any of its subclasses (as APIs don't exist to do this). This is why when you created a JAXBContext that was aware of Mammal for logging purposes everything worked correctly.

Solution #1 - Leverage @XmlSeeAlso

The @XmlSeeAlso annotation tells the JAXB impl that when your processing this class also process these other classes. Typically this is used to reference mapped subclasses.

@XmlRootElement
@XmlSeeAlso({Mammal.class})
public abstract class Animal {

    public String type;

    public Animal() {
        type = "?";
    }

    public Animal(String type) {
        this.type = type;
    }
}

Solution #2 - Leverage a JAX-RS ContextResolver

I mentioned the problem was due to the default JAXBContext that Jersey creates will not be aware of the Mammal class. You can use a ContextResolver to return a JAXBContext that is. Below is a link to an example of creating a ContextResolver:

Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400