Using Moxy with Jersey for XML Part 3

See part 1 for my initial confused ramblings, and part 2 for a workaround. This third and final part will hopefully bring some clarity to both the initial problem and the solution.

Firstly, a brief statement of how I went astray:

The use case

The requirement is to create a REST API for manipulating an existing domain model. While I do have access to the domain model source code, I’m reluctant to alter it for this specific scenario, so anything requiring annotating the domain model is out. Similarly, while it’s possible to create an intermediate model (e.g. a set of DTO’s) and annotate that, that approach has always seemed to me like excessive double handling. To my mind, XML is supposed to be the dumb DTO format. I know there are use cases which require all these layers, but I also know that most of the time YAGNI.

The solution sketch

  1. JAX-RS seems to be a nicely concise, annotation-driven way to create REST services on a servlet container
  2. Jersey is a JAX-RS implementation with some traction, and some useful integrations with various containers and format providers
  3. By using JAXB with Jersey, we remove the need to write the XML marshalling/unmarshalling layer
  4. JAXB by default is completely annotation-driven, but Moxy is a JAXB implementation that can be configured via an external mapping document. Happily, Jersey ships with Moxy integration.
  5. Finally, Moxy gives us JSON capability for free.

So that was the plan. Implement that stack of frameworks, create a mapping file, and serve up both XML and JSON based on the one unannotated domain model. There are lots of examples around to help get started:

plus many more linked from the above. There’s no example I could find that reproduces all the elements of my solution sketch – Jersey + JAXB + Moxy + external mapping file + unannotated file – but surely there’s enough to get started.

What the documentation doesn’t tell you

Spot the flaw in my thinking:

  1. Jersey integrates with Moxy out of the box – TRUE
  2. Moxy supports the use of an external mapping file – TRUE
  3. When you use Moxy with the external mapping, you don’t need any annotations on the model – TRUE
  4. The Jersey docs even show you how to configure Moxy with a mapping file – TRUE
  5. Therefore, you can use Jersey + Moxy without annotations on the model – FALSE

Stumbled at the last hurdle. To add to the bafflement, this all works flawlessly for JSON marshalling, but fails for XML. I’ll go into more detail below as to why this is so, but for now, here’s what you need to know:

Jersey’s Moxy integration does not expose all of Moxy’s functionality for the XML case. There is still some of the standard annotation-driven JAXB code in the mix, and to get past that your model must be annotated at least with @XmlRootElement

Once you’ve satisified Jersey’s default JAXB provider by adding that one annotation, Jersey will happily hand you over to Moxy and all the external mapping goodness works just fine.

Is there a better way?

But wait, I started off by saying I didn’t want to annotate my model. If you really don’t want to or can’t annotate the model, there is another way. You need to register your own provider. This example gives the basic idea, although I needed to tweak it a bit to get it to work (more on that below). This is in fact what Jersey’s Moxy code does for the JSON case, which is why that works and the XML case doesn’t.

Example code

Most of this example code is identical to all the other examples out there, but I’ll collect it here as the full description of what finally worked for me. This is using Jersey 2.9 and EclipseLink Moxy 2.5 on Java 7. This code can also be found on bitbucket.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>test</display-name>
  
  <servlet>
    <servlet-name>Jersey REST Service</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>testing</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
  
</web-app>

JAX-RS service definition

Without the custom provider described further down, this code works for the JSON case (/json) and the two endpoints that use the annotated model (/json2 and /xml2), but fails for the unannotated XML case (/xml).

package testing;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/services")
public class Services {

	@GET @Path("/json")
	@Produces(MediaType.APPLICATION_JSON)
	public Planets serviceListJSON () {
		return new Planets();
	}
	
	@GET @Path("/xml")
	@Produces(MediaType.APPLICATION_XML)
	public Planets  serviceListXML () {
		return new Planets();
	}
	
	@GET @Path("/json2")
	@Produces(MediaType.APPLICATION_JSON)
	public PlanetsAnnotated serviceListJSON2 () {
		return new PlanetsAnnotated();
	}
	
	@GET @Path("/xml2")
	@Produces(MediaType.APPLICATION_XML)
	public PlanetsAnnotated  serviceListXML2 () {
		return new PlanetsAnnotated();
	}
	
}

Model classes

package testing;

public class Planets {

    private int id = 1;
    private String name = "test";
    private double radius = 3.0;


}
package testing;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class PlanetsAnnotated {

    private int id = 1;
    private String name = "test";
    private double radius = 3.0;


}

Mapping file

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="testing"
    xml-mapping-metadata-complete="true"
    xml-accessor-type="NONE">
    <java-types>
        <java-type name="Planets">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="id"/>
                <xml-element java-attribute="name"/>
                <xml-element java-attribute="radius"/>
            </java-attributes>
        </java-type>
         <java-type name="PlanetsAnnotated">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="id"/>
                <xml-element java-attribute="name"/>
                <xml-element java-attribute="radius"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

jaxb.properties

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Context resolver

This is one of several ways to give Moxy the mapping file.

package testing;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.eclipse.persistence.jaxb.JAXBContextProperties;

@Provider
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class MyMoxyContextResolver implements ContextResolver<JAXBContext> {
    private JAXBContext context = null;
 
    public JAXBContext getContext(Class<?> type) {
	System.out.println("Invoking MyMoxyContextResolver.getContext");
    	if (context == null) {
	        try {
	            Map<String, Object> properties = new HashMap<String, Object>(1);
	            properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "testing/planets.oxm.xml");
	            context = JAXBContext.newInstance(new Class[] {Planets.class}, properties);
	        } catch(JAXBException e) {
	            throw new RuntimeException(e);
	        }
    	}
       return context;
    }

}

Custom provider

This is the part that makes the XML case finally work without annotations. For a full solution this would implement MessageBodyReader as well.

package testing;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

@Provider
@Produces("application/xml")
public class MyMessageBodyWriter implements MessageBodyWriter<Object> {

	@Context
	protected Providers providers;

	@Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return true;
    }
 
    @Override
    public long getSize(Object les, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }
 
    @Override
    public void writeTo(Object les, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        try {
        	ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, mediaType);
        	JAXBContext context = resolver.getContext(type);
        	
            context.createMarshaller().marshal(les, entityStream);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
    
}

Source code layout

This is my example structure for a gradle project.

src/
   main/
      java/
           jaxb.properties
           MyMessageBodyWriter.java
           MyMoxyContextResolver.java
           Planets.java
           planets.oxm.xml
           PlanetsAnnotated.java
           Services.java
   webapp/
      WEB-INF/
           web.xml 

Help!

This is a super-simple proof-of-concept. No doubt it has all sorts of sub-optimal choices, and for all I know breaks all sorts of other use cases while solving mine. However, as far as I can tell there is no other example out there for this specific scenario. It would be great if this could be evolved into something more robust, so please let me know if you have any suggestions.

The gory details

This is a sketch of what’s happening in the innards of Jersey when using JAXB to marshal the results of a service call. I’m including this as background to any future discussion on what a supported solution might look like. For example, maybe MoxyXmlFeature should register a provider that relaxes the requirement for @XmlRootAnnotation.

Jersey

At startup time:

  • Jersey registers built-in providers
  • Features (discovered on the classpath) register providers and context resolvers
  • The jaxb.properties file defines the JAXBContextFactory to be used

At request processing time:

  • The annotated JAX-RS service method returns a Java object
  • Jersey’s interceptor fires (triggered by the @Produces annotation)
  • Jersey searches its list of providers based on the class of the returned Java object and on the media type required
  • The chosen provider searches for a context resolver that is configured to provide a context for the Java class
  • The chosen context resolver obtains and configures a JAXBContext and returns it to the provider
  • The JAXBContext returns a marshaller
  • The provider invokes the marshaller, which emits the marshalled representation of the Java object

Each provider has a list of rules defining what it will handle. For example, there’s a built-in XmlRootElementProvider, which will only handle classes annotated with @XmlRootElement for media type application/xml. If no matching provider can be found, an exception is thrown.

So, on to Moxy. The jersey-media-moxy module, which ships with Jersey and provides Moxy integration, registers two features – MoxyJsonFeature and MoxyXmlFeature.

MoxyJsonFeature registers a provider, ConfigurableMoxyJsonProvider, which will handle marshalling any object where the media type is application/json. You can then use either the MoxyJsonConfig method or register your own ContextResolver to configure a mapping file if so desired.

MoxyXmlFeature, on the other hand, does not register a provider. For annotated models, one of Jersey’s built-in providers will work just fine, but for non-annotated models that means that no provider will be found and an exception will be thrown. MoxyXmlFeature does register a context resolver, thus providing a mechanism for configuring a mapping file, but without any annotations to get you past the Jersey provider that context resolver will never be invoked. Hence the need to create our own provider.

6 thoughts on “Using Moxy with Jersey for XML Part 3

  1. Jersey/Moxy does provide a jaxb context resolver and registers it.
    All I had to do was add a custom Application class to call .register(new MoxyXmlFeature(…, true, …)) to enable loading XML bindings.
    Then you put the binding file eclipse-oxm.xml in the same package as the object you want to bind to.
    Note that as you mentioned I did need to mark objects with XmlRootElement for them to be picked up.
    However I did not need to write any providers (context resolvers, message body writers, etc).

  2. @Roger,
    Yes, only the custom MessageBodyWriter is really required to get things working with zero annotations. The custom context resolver is handy for selecting different representations (for example, more or less verbose versions of an object), but that isn’t used in my example.
    I’m interested, though, in the idea of having a model that you actually can annotate. In my context the domain model is a high-business-value encapsulation of complex domain logic, and even though I have access to the source there’s no way I’m going to clutter it up with annotations specific to JAXB (or JPA or anything else, for that matter). However, from the number of frameworks that expect you to do just that, I presume it’s common practice to use some sort of intermediate model. Unless people’s models are just a forest of annotations, the question in my mind is, how do you get from a sacrosanct high-value model to an intermediate representation suitable for JAXB without absurd amounts of boilerplate. Or does this problem just not arise that often?

  3. Yeah if you use no annotations at all then the custom classes were required to get Jersey to handle it.
    I was using @XmlRootElement on mine, so required no custom providers at all (just using the built in ones).
    However, I do typically use annotations too as there are downsides to not.
    e.g. If I use XML binding config for some content on the object it works, but then if I include that object in another object then I need to duplicate the XML binding config for that second object too. I found that to be far too annoying.
    Personally I’ve found it best to have a separation of objects, so service objects vs business objects where the annotation mappings are on the service objects etc.
    It means you have more objects to write and some mapping, but it does provide flexibility for extension/customization and separation of layers can help with containing tech dependency and data processing (sanitizing etc), etc.

  4. Roger, not sure what you mean by “If I use XML binding config for some content on the object it works, but then if I include that object in another object then I need to duplicate the XML binding config for that second object too.”
    If you look at the example code in my later post (http://lagod.id.au/blog/?p=494), the XML binding for the Moon class works just fine whether you’re marshaling a standalone Moon or a Planet with Moons. Are you talking about some other situation?
    As for the extra service layer – yep, that’s fine a lot of the time. As you say, you can do other things with that layer. But if the only reason it exists is for pre-marshaling – well, I think that’s something the framework is perfectly capable of handling. Either way, there’s no design reason for the difference between the JSON and the XML cases, rather (I suspect) just a consequence of the implementation path. In the JSON case the developers of the Moxy feature had to write a provider, and in the XML case they had the option to re-use the existing Jersey provider, thereby creating an accidental divergence in capability. They might take the view that it wasn’t a design decision to make even the JSON case work annotation-free.

  5. Yeah I think it’s a bit of a disconnect between the Moxy and Jersey. Moxy is more JAXB focused, so to get it to work without any XML related info needs some adaption and won’t even be detected if done purely through external XML bindings (eclipse-oxm.xml).
    What I had meant was, I had marked some objects with the XmlRootElement annotation, then used external XML bindings to add Moxy specific configuration in (e.g. XmlVariableNode) so there was no compile dependency on Moxy.
    This works when the object is a top level element, but when nested in another object the configuration from the XML was not applied to the nested version.
    Note this is when not using any custom writers/providers.
    In the end I had restructured my packaging to allow Moxy dependencies in the server layer while keeping Jersey limited to a service web module and then used only annotations.

Leave a Reply

Your email address will not be published. Required fields are marked *