Search code examples
javajaxbjax-rsjava-ee-6

JAXB "nor any of its super class is known to this context" avoid @XmlSeeAlso


Explanation & Workaround

Currently I am using JAX-RS and letting JAXB bindings automatically handle converting the data to XML and JSON for me in a JEE6 project. Everything is working absolutely fantastically until I try to create a generic response object to wrap all of my information in.

When I attempt to use my generic response class com.eln00b.Wrapper (which contains a private Object result attribute within it) I get:

javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: class com.eln00b.CustomObject nor any of its super class is known to this context. javax.xml.bind.JAXBException: class com.eln00b.CustomObject nor any of its super class is known to this context.]

So I add to com.eln00b.Wrapper:

@XmlSeeAlso ({com.eln00b.CustomObject})
public class Wrapper {
}

Everything works fine.

The Problem

I want this to be extremely generic. I do not want t constantly add classes to the @XmlSeeAlso annotation on the com.eln00b.Wrapper class. How do I have the system automatically locate all of my classes for the JAXB context?

Even if it's a hack where I use something like Reflections to load the data, that's fine. I'm just not sure how to get the context to load all of that data without the @XmlSeeAlso annotation. With the large amount of annotations I will be creating it will just simply not work.

How It Worked Manually

It worked manually just by adding the data like so doing manual conversions. However, I do not want to use manual XML/JSON creation unless I absolutely need to (I don't want to deal with content negotiation or anything like that).

Sample:

JAXBContext.newInstance(new Class[] {Wrapper.class, CustomObject.class});

Solution

  • So here is what the essence of the custom resolver looks like:

    @Provider
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public class JaxbContextResolver implements ContextResolver<JAXBContext> {
    
      @Override
      public JAXBContext getContext(Class<?> type) {
        // load appropriate context data
        Class[] bindTypes = ...
    
        // create 
        try {
          return JAXBContext.newInstance(bindTypes);
        } catch (JAXBException e) {
          // todo:  this can be handled better but works for the example
          throw new RuntimeException(e);
        }
      }
    }
    

    Now, the processing for "load appropriate context data" is pretty simple. By basically mimicking @XmlSeeAlso using runtime data:

    1. Create a custom something (annotation, processing method, whatever) that marks a particular field/method as "contextual"
    2. Load the field/method data pulling the data types out
    3. Make sure you do not load duplicates and check for infinite recursion possibilities

    Now, I used some caching to help make things more efficient for myself. I also created a slightly more complex setup for my root object where it actually kept track of the class data on its own and made it pretty speedy. I also created an alternative that marked classes as "contextual" that I used package inspection to load via annotations and just automatically add to the context but I have not checked efficiency on that yet. I have some ideas for a 3rd implementation, but I want to get more benchmarking completed.