Jose Sandoval Google
 Resume     Book     Software     Drawings     Home     Subscribe to RSS feed Search Web Search josesandoval.com

Struts 2 REST Plug-in with HTTP Accept header
Sunday, July 26, 2009

Out of the box, the Struts 2 REST plug-in doesn't handle the HTTP Accept header. This is a problem if you want to create a true RESTful Java web service, which is supposed to use the communication protocol for content negotiation between client and server.

What the plug-in offers is content negotiation via the URI, which is not a RESTful call. For example, assume that we have a URI that returns a list of users, like so /users. If we wanted to use the provided content negotiation of the plug-in, and if we wanted an XML representation, we would use the GET request /users.xml. The plug-in makes the automatic conversion of the model in an action class, provided that we implement the com.opensymphony.xwork2.ModelDriven interface (I'll cover how below). The plug-in also provides automatic JSON model representations, with a call to /users.json. On the plus side, we can extend the plug-in's MIME type handlers (I won't cover this here); nevertheless, whatever you do, the framework will not handle the Accept header of the HTTP request.

Can we use Struts 2 with the REST plug-in to properly handle the Accept header? We can, but we have to code around the plug-in's limitations. There are two ways of doing this: on the one hand, we can code a Struts 2 interceptor; on the other hand, we can do what I will explain below. None of the 2 choices available are out of our reach; however, the following solution is easier to understand.

This is the code (see below for the details):
import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.rest.DefaultHttpHeaders;
import org.apache.struts2.rest.HttpHeaders;

import com.opensymphony.xwork2.ModelDriven;

public class UsersController
implements ModelDriven<Object>, ServletRequestAware {
private String representation;
private HttpServletRequest request;

// GET /users
public String show() {
String acceptHeader = request.getHeader("Accept");
if (acceptHeader == null || acceptHeader.isEmpty()
|| acceptHeader.equals("application/xml")) {
representation = BACK_END_SERVICE.getXML();
} else if (acceptHeader.equals("application/json")) {
representation = BACK_END_SERVICE.getXML();
}

return "SUCCESS";
}

public String getRepresentation() {
return representation;
}

public Object getModel() {
return representation;
}

public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
}
This is what's happening: first, we make the action class request aware by implementing the interface org.apache.struts2.interceptor.ServletRequestAware; second, in the method handling GET requests for the URI /users (the index() method), we grab the value of the Accept header of the HTTP request; and, third, we make a decision as to which representation we need to send, depending on the client's preference.

Let me first talk about handling of the Accept header, and then I'll explain the ModelDriven interface.

Like I explained above, we need to implement the org.apache.struts2.interceptor.ServletRequestAware interface. Struts 2 action classes don't make the HTTP request object available unless specifically told to do so. Once this interface is implemented we get access to the javax.servlet.http.HttpServletRequest instance; the instance is available in the private variable request. When the action class is instantiated by an HTTP request, the framework updates the reference with this method:
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
We now have access to the request instance of the action class, and, therefore, we have access to the HTTP Accept header, as follows:
String acceptHeader = request.getHeader("Accept");
We now decide what representation to send: if the value of the header is empty or application/xml, we use our backend service to get an XML representation; if the value is application/json, we get a JSON representation. I'm highlighting the method here:
public String show() {
String acceptHeader = request.getHeader("Accept");
if (acceptHeader == null
|| acceptHeader.isEmpty()
|| acceptHeader.equals("application/xml")) {
representation = BACK_END_SERVICE.getXML();
} else if (acceptHeader.equals("application/json")) {
representation = BACK_END_SERVICE.getXML();
}

return "SUCCESS";
}
And there you have it. Our web service is now able to properly handle the HTTP Accept header. More important, GET requests to /users are now RESTful calls that don't rely on the URI for content negotiation.

I said I was going to talk about the com.opensymphony.xwork2.ModelDriven interface. This interface is not part of the REST plug-in; it's actually part of the Struts 2 framework. The interface is useful for the cases when your action class is not the actual model of the application. In other words, your action class references an actual value object class. So implementing the interface means letting the framework know what type of object to work with for every request (the details are out of the scope of this entry, but it uses reflection to massage the model object). In my example here, I'm letting the action class know that my model is a String object with a name of representation. The model gets set with the only implemented method of the interface, as follows:
public Object getModel() {
return representation;
}
So, how does the plug-in provide automatic representations with URI calls? This is where the model comes into play. The REST plug-in has content handler classes that read in the model to automatically convert it into an XML or a JSON structure. It's a pretty neat trick; however the functionality is limited, because it's conceivable that we don't want our Java models to be directly represented as XML structures to the client--that would be opening our API too much and violates code encapsulation.

As an aside, a Struts 2 action class is called a REST controller when we are talking about the REST plug-in; however, a REST controller class is the same thing as an Struts 2 action class. The differentiation just makes it easier to distinguish between other action classes in a project.

In my book, RESTful Java Web Services, I use Struts 2 together with the REST plug-in to code a RESTful web service. I also cover this very limitation among others.


8:03 PM | 1 comment(s) |

Comments:

Hi Jose,

Thanks for the wonderful tutorial and an amazing book. I have a copy of your book and it helped me alot in my work. I have a question for you.

I have a web application which has rest as well as normal web actions. I've configured rest-plugin but now if I define a normal action in my struts.xml, it gives me an exception that NoSuchMethod .... index().

I know I am missing some configuration here, but what is it? I'll be very thankful to you if you could guide me. Well I already am :).

Thanks,
Emtiaz



This page is powered by Blogger. Isn't yours?

Guestbook
© Jose Sandoval 2004-2009 jose@josesandoval.com