Structural linking with Jersey + Moxy – an AspectJ solution

The requirement

In a REST resource representation, structural links can be embedded in the resource to allow the client to navigate to subresources. This is in contrast to so-called transitional links, which represent state transitions.

    <parent id="100">
        <!-- I am a transitional link -->
        <link href="http://myserver/parents/100" rel="delete">  

        <!-- We are child resources with structural links to our full representation -->
        <child id="1" href="http://myserver/children/1"/>       
        <child id="2" href="http://myserver/children/2"/>
    </parent>

The JAX-RS project has a good discussion of the differences, as well as some explanation of why transitional links are so much easier to support than structural links.

I’ve been struggling to find a good way to implement structural links, especially as I want to add a couple more constraints:

  1. The domain model must not be touched. Even when you do have access to the source code, I simply don’t think it’s kosher to pollute a domain model with cross-cutting concerns like serialization to a particular format.
  2. I won’t put up with death-by-boilerplate, such as creating a DTO for each domain model class.

I posted this problem as a Stack Overflow question along with some candidate approaches. This blog post is about the first candidate, the AspectJ solution.

The solution sketch

This solution uses Jersey’s very handy declarative linking capability. You can add a field to a model class and Jersey will populate it for you. Even more impressively, you can simply point Jersey at a method in your REST API and Jersey will derive your link from the @Path annotations on that method. Too easy. Just too bad this violates my #1 constraint, that the model not be touched.

Enter AspectJ. We can use an intertype declaration to add the field, and everything just works.

The downside of this solution is that the structural link configuration is not truly externalized (with respect to the model). Yes, we’ve avoided altering the model source code – which, IMHO, is no small win, as preserving the expressive power of the model source code is a pretty high priority for me. However, at the byte code level, we have most definitely altered the model, at least within this compilation unit. The new fields will be visible to any reflection-driven framework and may pop up in other representations. If we can live with that, this is a pretty clean solution.

Example code

This code is also available on BitBucket.

Model classes

package testing;

import java.util.ArrayList;
import java.util.List;

public class Planet {

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

    private List<Moon> moons = new ArrayList<Moon>(0);
    
    public void addMoon(Moon moon) {
    	moons.add(moon);
    }
}
package testing;

public class Moon {
	
	private String name;
	
	// No-arg constructor is a requirement of JAXB
	public Moon() {
	}
	
	public Moon(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

}

Resource class

For the proof-of-concept, we’re just serving an freshly created instance of the model.

package testing;

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

@Path("/services")
@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public class Services {
	
	private Planet initPlanet() {
		Planet p = new Planet();
		p.addMoon(new Moon("moon1"));
		p.addMoon(new Moon("moon2"));
		return p;
	}

	@GET
	public Planet planet () {
		return initPlanet();
	}
	
	@GET @Path("/moons/{moonid}")
	public Moon moon (@PathParam("moonid") String name) {
		return new Moon(name);
	}
	
}

Jersey configuration

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>javax.ws.rs.Application</param-name>
	    <param-value>testing.MyApplication</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>

Application class

This is the class pointed to by the web.xml, and it really just exists so we can register the declarative linking feature.

package testing;

import org.glassfish.jersey.linking.DeclarativeLinkingFeature;
import org.glassfish.jersey.server.ResourceConfig;

public class MyApplication extends ResourceConfig {
	
    public MyApplication() {
        packages("testing");
        register(DeclarativeLinkingFeature.class);
    }

}

Moxy configuration

Moxy’s ability to configure JAXB mappings entirely from an external file is a cornerstone of my approach to serving up unmodified domain objects.

jaxb.properties

This is how we let Jersey know we want to use Moxy as the JAXB provider.

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

The moxy mapping file, planets.oxm.xml

Note that the href elements mentioned in the mapping file do not exist in the model source code.

<?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="Planet">
            <xml-root-element/>
            <java-attributes>
	           	<xml-attribute java-attribute="href"/>
                <xml-element java-attribute="name"/>
                <xml-element java-attribute="radius"/>
 				<xml-element java-attribute="moons" name="moon">
 					<xml-element-wrapper name="moons"/>
 				</xml-element> 	
            </java-attributes>
        </java-type>
        <java-type name="Moon">
            <xml-root-element/>
        	<java-attributes>
	           	<xml-attribute java-attribute="href"/>
        		<xml-element java-attribute="name"/>
        	</java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Moxy context resolver

This is how we supply Moxy with the correct mapping file. The @Provider annotation lets Jersey automatically find our context resolver.

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 || context != null) {
	        try {
	        	System.out.println("MyMoxyContextResolver - Creating new JAXBContext");
	            Map<String, Object> properties = new HashMap<String, Object>(1);
	            properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "testing/planets.oxm.xml");
	            context = JAXBContext.newInstance(new Class[] {Planet.class}, properties);
	            
	        } catch(JAXBException e) {
	            throw new RuntimeException(e);
	        }
    	}
       return context;
    }

}

Custom MessageBodyWriter

As per this StackOverflow question, we need to add a custom MessageBodyWriter to convince Jersey to marshall a completely unannotated model. Again, Jersey picks this up automatically.

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;
import javax.xml.bind.Marshaller;


@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);
        	
        	Marshaller m = context.createMarshaller();
			m.marshal(les, entityStream);

        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
    
}

The aspect

This is what makes it all work. As this project is laid out all in one package, all you need to do is compile with the AspectJ compiler and this aspect will be woven into the model classes. In a more realistic project, you’d have the model classes on the inpath and this aspect on the aspectpath.

package testing;

import org.glassfish.jersey.linking.InjectLink;
import org.glassfish.jersey.linking.Binding;

public aspect HrefInjector {
	
	private String Planet.href;
	declare @field : * Planet.href : @InjectLink(
										resource=Services.class, 
										style=InjectLink.Style.ABSOLUTE
									) ;

	private String Moon.href;
	declare @field : * Moon.href : @InjectLink(
										resource=Services.class,
										method="moon",
										bindings={@Binding(
												name="moonid", value="${instance.name}"
												)},
										style=InjectLink.Style.ABSOLUTE
									) ;

}

Project layout

Screenshot from 2014-06-27 16:40:30

(click to enlarge)

Dependencies

As they appear in the gradle build file:

dependencies {
   compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.9'
   compile 'org.glassfish.jersey.media:jersey-media-moxy:2.9'
   compile 'org.glassfish.jersey.ext:jersey-declarative-linking:2.9'
   
   providedCompile 'javax.servlet:servlet-api:2.5'
}

As always, you can run

./gradlew dependencies

to see the full list of transitive dependencies.

Sample output

XML

	<planet href="http://localhost:8080/reststructlinks/rest/services">
		<name>test</name>
		<radius>3.0</radius>
		<moons>
			<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon1">
				<name>moon1</name>
			</moon>
			<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon2">
				<name>moon2</name>
			</moon>
		</moons>
	</planet>

JSON

{
  "href": "http://localhost:8080/reststructlinks/rest/services",
  "name": "test",
  "radius": 3,
  "moons": {
    "moon": [
      {
        "href": "http://localhost:8080/reststructlinks/rest/services/moons/moon1",
        "name": "moon1"
      },
      {
        "href": "http://localhost:8080/reststructlinks/rest/services/moons/moon2",
        "name": "moon2"
      }
    ]
  }
}

Conclusion

A few concluding comments:

  1. There are only two source files concerned with structural linking – the mapping file, and the aspect. The rest of the system operates exactly as per usual. This satisifies the requirement for structural linking to be separated out as a crosscutting concern.
  2. It’s entirely appropriate that the mapping file is involved, as it means you can control when and where you want the structural links by modifying the mapping file.
  3. The aspect requires no special configuration, as it is using bog-standard built-in Jersey functionality.
  4. Jersey’s built-in UriBuilder approach to transitional linking co-exists happily with this aspect-driven approach.
  5. Of course, some people/projects are allergic to AspectJ for whatever reason, so it would still be good to see another candidate solution emerge.

Hope this helps!

Leave a Reply

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