Question

I have REST (Resteasy) client running under JBOSS 7.1. The response coming back from server requires me to use my own JAXB ObjectFactory since the same Java type is mapped to multiple XML elements:

@XmlRegistry
public class ObjectFactory {

    private final static QName RESPONSE_QNAME = new QName("response");
    private final static QName ORDER_QNAME = new QName("order");

    @XmlElementDecl(name="response")
    public JAXBElement<PurchaseResponse> getPurchaseResponse(PurchaseResponse r) {
        return new JAXBElement<PurchaseResponse>(RESPONSE_QNAME, PurchaseResponse.class, null);
    }

    @XmlElementDecl(name="order")
    public JAXBElement<PurchaseResponse> getOrderResponse(PurchaseResponse r) {
        return new JAXBElement<PurchaseResponse>(ORDER_QNAME, PurchaseResponse.class, null);
    }

I build RestEasy client like this:

    ResteasyClient client = new ResteasyClientBuilder().build();
    ResteasyWebTarget httpTarget = client.target( endpoint );
    RestService target = httpTarget.proxy(RestService.class);
            Response resp = target.callAPI();
            return resp.readEntity(PurchaseResponse.class);

where RestService is interface representing REST API methods. This works provided PurchaseResponse class has XmlRootElement annotation on the class. As soon as I remove it the readEntity() call fails with 'cannot find message body processor'. However, I dont want JAXB to use XmlRootElement annotation but XmlElementDecl annotations in my ObjectFactory, and its not happening.

I can make it work by explicitly building JAXB context for my package, manually reading response into string, then parsing it into XML document and finally feeding into JAXB unmarshaller generated by the context (ugh!). I dont want to do this every time I call the API.

Why isnt the default JAXB unmarshaller picking up my ObjectFactory class and using it? I use locally bundled RestEasy libs (JBOSS-provided RestEasy module is archaic).

Was it helpful?

Solution 2

After digging into Resteasy sources it seems to me that target class has to be annotated with XmlType in order for RE to start looking for the ObjectFactory:

public abstract class AbstractJAXBContextFinder implements JAXBContextFinder
{
   protected static final String OBJECT_FACTORY_NAME = ".ObjectFactory";

   public static Class<?> findDefaultObjectFactoryClass(Class<?> type)
   {
      XmlType typeAnnotation = type.getAnnotation(XmlType.class);
      if (typeAnnotation == null) return null;
      if (!typeAnnotation.factoryClass().equals(XmlType.DEFAULT.class)) return null;
      StringBuilder b = new StringBuilder(getPackageName(type));
      b.append(OBJECT_FACTORY_NAME);
      Class<?> factoryClass = null;
      try
      {
         factoryClass = Thread.currentThread().getContextClassLoader().loadClass(b.toString());
      }
      catch (ClassNotFoundException e)
      {
         return null;
      }
      if (factoryClass.isAnnotationPresent(XmlRegistry.class)) return factoryClass;
      return null;
   }
}

I added @XmlType annotation on the class, and RestEasy readEntity() call is now unmarshalling properly. I still have to run full tests, but right now it seems to me like this works.

Seems like a bug in Resteasy: object factory should also be loaded for @XmlRootElement annotated classes.

OTHER TIPS

When a JAXBContext is build on a class (or classes) it won't automatically pulled in. lYou can create a ContextResolver to control the JAXBContext that is used by a JAX-RS implementation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top