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.

Using Moxy with Jersey for XML Part 2

In part 1 I described the ways in which the documented approach doesn’t work. This post is about my first workaround, but for a comprehensive debrief, see part 3.

The approach that works is:

  1. Build with the jersey-media-moxy module – so
  2. Put the jaxb.properties file in the right place – so
  3. Create a mapping file – so
  4. Create a ContextResolver – example. This will be used for JSON but not for XML.
  5. Create a MessageBodyWriter – example. This will be used for XML.
  6. Most importantly, write and let me know where on earth I went wrong to need to jump through all these hoops 🙂
  7. Check out this little demo project to see how it all fits together.

Using Moxy with Jersey for XML

This post is about my initial attempts to get Jersey and Moxy working the way I want. See part 3 for the full analysis of why I was having trouble and my eventual solution.

Jersey is an implementation of JAX-RS, the Java standard for REST web services. To this you can add JAXB, which allows your REST services to use declarative metadata to map domain objects to a wire format, typically either JSON or XML.

The downside of the JAXB reference implementation is that the metadata is expressed as annotations on the domain model. Personally, I think that this kind of mapping is definitely a cross-cutting concern and is to be kept out of the domain model source code at all costs. Moxy comes to the rescue! You can plug Moxy in as your JAXB provider, replacing the reference implementation entirely, and Moxy supports externalizing the mapping metadata to an XML file. So, how to get it working?

The Jersey documentation is very simple and to the point. To use Moxy for JSON binding, just place the appropriate jar on the classpath – see instructions. You can then use a custom JAXB context resolver to set Moxy up with a mapping file, and away you go.

The documentation for using Moxy for XML binding is even simpler. The simplest way is:

… use the standard JAXB mechanisms to define the JAXBContextFactory from which a JAXBContext instance would be obtained (for more on this topic, read JavaDoc on JAXBContext)…

That boils down to putting a jaxb.properties file alongside your model, as documented here.

The alternative mechanism is to manually configure Jersey, registering and configuring the MoxyXmlFeature.

However, neither mechanism actually works. It appears that, while Jerseys Moxy JSON feature registers a MessageBodyWriter to handle JSON writes, the XML feature does not do anything of the sort, instead falling back onto Jersey’s default XML MessageBodyWriter. Unfortunately, the default XML writer appears to completely ignore any registered ContextResolvers you’ve registered. Thus, while you can marshall a completely unannotated domain object to JSON, if you try to marshall the same object to XML you’ll get an exception.

See https://bitbucket.org/jmetcher/resttest for a small project showing Jersey setup strictly according to the documentation. Check out the master branch for the basic setup.

The solution is to register your own custom MessageBodyWriter, as described here. Sadly, as far as I can tell this is 100% missing from the documentation. Check out the MBW branch of the above project to see what it looks like with the recommended MessageBodyWriter approach. Unfortunately, this doesn’t work either. It appears that, contrary to about a dozen examples dotted around the web, injection of the ContextResolver does not work, as you’ll see by the NPE if you run that code.

Finally, have a look at the MBW_Better branch of the project. As this one actually works, I’ll pull it out into a separate post.