Search code examples
cxfjax-rssuperclass

JAX-RS & CXF map request to method based on XML


Hopefully I got the title right, but I'm trying to map requests using JAX-RS @Path annotations to different methods based on the entity parameter.

I think example code will make it easier:

Super class:

public class Geothing {
    private int thingId;
    private String status;

    // Ctor, getters and setters
}

PolygonGeothing extends Geothing:

@XmlRootElement
public class PolygonGeothing extends Geothing {
    private Coordinates coordinates;

    // Ctor, getters and setters
}

CircleGeothing extends Geothing:

@XmlRootElement
public class CircleGeothing extends Geothing {
    private Coordinate center;
    private int radius;

    // Ctor, getters and setters
}

Service interface:

@Path("/geothing/{id}")
public interface GeothingService extends CoService {
    @POST
    Response createGeothing(@PathParam("{id}") Long id, PolygonGeothing geothing);

    @POST
    Response createGeothing(@PathParam("{id}") Long id, CircleGeothing geothing);
}

My expectation was that if I POSTed XML for a PolygonGeothing or CircleGeothing then it would work. However, it only works if I POST PolygonGeothing XML and if I POST CircleGeothing XML then I get a JAXBException:
JAXBException occurred : unexpected element (uri:"", local:"circleGeothing"). Expected elements are <{}polygonGeothing>.

Is it possible to have this mapped correctly without having to specify a separate URI path for CircleGeothing and PolygonGeothing? Furthermore, is it possible to have an interface such as the following, where I can use the super-class as the parameter? I tested returning type Geothing and if I create a PolygonGeothing or CircleGeothing and return it then it works fine... but not if I try to pass PolygonGeothing or CircleGeothing as a parameter where the param type is Geothing.

@Path("/geothing/{id}")
public interface GeothingService extends CoService {
    @POST
    Response createGeothing(@PathParam("{id}") Long id, Geothing geothing);
}

Solution

  • It's impossible to do it this way. The reason is simple: CXF (as any other JAX-RS framework) routs the responses based on REST definitions and not based on object oriented principals. I mean that it selects the method based on URL, produced content type and consumed content type. (This is simplified description, see the JSR311 for exact algorithm). But it has nothing to do with objects you expect. Objects are getting serialized only after the method is already selected. This is the reason you are getting the exception: createGeothing(@PathParam("{id}") Long id, PolygonGeothing geothing) is selected, so it tries to serialize the wrong class.

    You can do the following:

    Response createGeothing(@PathParam("{id}") Long id, Geothing geothing)
    

    and than cast and call the relevant method.
    Of course you need to make sure that Geothing is known to JAXB context.