Control JAX-RS Content Negotiation with Filters

January 18th, 2010 by Henri Bezemer

JAX-RS supports very sophisticated content negotiation, built around the HTTP accept header and the @consumes and @produces annotations. Unfortunately some (potential) RESTful clients are not so sophisticated. Try your webbrowser for instance. It is not always easy or even possible to set the HTTP accept header to the desired value. In this post I’ll present a simple Filter (javax.servlet.Filter) that dynamically adds HTTP accept headers to your requests, based on a predefined URI pattern. It has been tested with the Jersey implementation of JAX-RS.

A peek at the Twitter API

The Twitter API allows content negotiation based on a “file type extension” pattern of the URI. For example, here are four different ways to retrieve the public timeline:

The Filter

Following the Twitter API approach, I wrote the following Filter:

package com.zienit.simplerest;

import java.io.IOException;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class AcceptServletFilter implements Filter {

	private HashMap<String,String> map = new HashMap<String,String>();

	@SuppressWarnings("unchecked")
	public void init(FilterConfig config) throws ServletException {
		Enumeration<String> names = config.getInitParameterNames();
		while (names.hasMoreElements()) {
			String name = names.nextElement();
			map.put("."+name,config.getInitParameter(name));
		}
	}

	public void destroy() {}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		chain.doFilter(new Wrapper((HttpServletRequest)request),response);
	}

	private class Wrapper extends HttpServletRequestWrapper {

		public Wrapper(HttpServletRequest request) {
			super(request);
		}

		private String getExtension(String path) {
			int index = path.lastIndexOf('.');
			if (index == -1) {
				return "";
			}
			String extension = path.substring(index);
			System.out.println("searching for: " + extension);
			if (map.get(extension) == null) {
				return "";
			}
			return extension;
		}

		@Override
		public String getRequestURI() {
			String uri = super.getRequestURI();
			return uri.substring(0,uri.length() - getExtension(uri).length());
		}

		@Override
		@SuppressWarnings("unchecked")
		public Enumeration getHeaders(String name) {
			if ("accept".equals(name)) {
				String type = map.get(getExtension(super.getRequestURI()));
				if (type != null) {
					Vector<String> values = new Vector<String>();
					values.add(type);
					return values.elements();
				}
			}
			return super.getHeaders(name);
		}
	}
}

Any class that implements the javax.servlet.Filter interface must implement 3 methods: init(), destroy() and doFilter(). Here, the destroy() method does noting. The init() method however initializes a mapping of (zero or more) file type extensions to mime types. This data is retrieved from the web.xml deployment descriptor. We’ll look at that file in a moment.

The doFilter() method does the real work. Here you can alter requests, responses and control the execution of filters and the servlet upstream. Because we want to alter certain properties of the request (namely a header and the URI) for which there are no “setters” available, we have to resort to other means to get that done. The Servlet API provides special wrapper classes which can be used for this purpose. We extend class HTTPServletRequestWrapper with an (inner) class. This class wraps HTTPServletRequest, which means that each of its methods simply call the same method on the wrapped object. The whole idea is that you override certain methods to get a more interesting functionality. Now keep in mind that the next recipient of the request will be the JAX-RS runtime. To trick the JAX-RS runtime, we’re overriding the behavior of getRequestURI() and getHeaders(). In getRequestURI(), we make sure to remove the extension part from the original requested URI (so public_timeline.xml becomes public_timeline). If getHeaders() is invoked to retrieve the value of the “accept” header, then we return the specified mime-type (but only if we’ve specified this in the deployment descriptor). The private method getExtension() is just a little helper.

web.xml

You probably already have written a web.xml deployment descriptor for your JAX-RS application. Look at the fragment below. Add this fragment to web.xml, but make sure this appears before the definition of the servlet (or filter) that drives the JAX-RS implementation, because the order of appearance determines the filter chain:

	<filter>
		<filter-name>accept-filter</filter-name>
		<filter-class>com.zienit.simplerest.AcceptServletFilter</filter-class>
		<init-param>
			<param-name>xml</param-name>
			<param-value>application/xml</param-value>
		</init-param>
		<init-param>
			<param-name>json</param-name>
			<param-value>application/json</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>accept-filter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

In this example I’ve mapped the extensions .xml and .json to mime types application/xml and application/json respectively. Change this to whatever suits your needs.

That’s all. Check out my other posts on REST and JAX-RS:

No Comments

Comments are closed.