The sample application developed in this tutorial will handle a simple message system, where it’s possible to read, add and remove messages.
So, the basic bean will be defined in the Message
class:
package com.zulutown.struts2.rest; public class Message { private String id; private String text; private String author; public Message() { super(); } public Message(String id, String text, String author) { super(); this.id = id; this.text = text; this.author = author; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Message other = (Message) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } }
The message will be identified by the id
property and will contain its text
and its author
in the other two class properties.
The next step is to setup a basic business service that will handle the basic operations on the messages: find, save, remove and so on. In a real world application this service would be a singleton that interacts with a database (through JDBC or JPA) but in this little demo the data model is just a very basic (and ugly) Map kept in a static property of the Message
class.
package com.zulutown.struts2.rest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MessageService { public static Map messages = new HashMap(); private static int nextMessageId = 4; static { Message message1 = new Message("1", "hello", "john"); Message message2 = new Message("2", "world", "ted"); Message message3 = new Message("3", "rest", "sam"); messages.put("1", message1); messages.put("2", message2); messages.put("3", message3); } public static List findAll() { return new ArrayList(messages.values()); } public static Message find(String id) { return messages.get(id); } public static void save(Message message) { if (message.getId() == null) { String id = String.valueOf(nextMessageId); message.setId(id); nextMessageId++; } messages.put(message.getId(), message); } public static void remove(String id) { messages.remove(id); } }
In messages
it is stored the data and the static initializer of the MessageService
class will populate the data model with three messages.
Then, some utility methods are provided: findAll()
to retrieve all the messages, find(String id)
to retrieve a message with a specific id
, save(Message message)
to add (or update) a message in the data model and, finally, a remove
method to delete a specific message.
Until now, this tutorial is not related at all with Struts 2.1, but now it is required to add a “web layer” to the application, and the REST capabilities provided by Struts2 will be used.
The first task is to add to the Web application the .jar
libraries of Struts2. Those files must be placed into /WEB-INF/lib
(in WebContent) that is the standard location where a Java Web Application expects to find its dependencies.
Download from the Struts website the archives with the libraries for Struts 2.1.x (currently the GA release is 2.1.6), unpack it, and copy & paste the following jars in /WEB-INF/lib/
of your project.
- commons-beanutils-1.7.0.jar
- commons-collections-3.2.jar
- commons-fileupload-1.2.1.jar
- commons-io-1.3.2.jar
- commons-lang-2.3.jar
- commons-logging-1.0.4.jar
- ezmorph-1.0.3.jar
- freemarker-2.3.13.jar
- json-lib-2.1.jar
- ognl-2.6.11.jar
- struts2-convention-plugin-2.1.6.jar
- struts2-core-2.1.6.jar
- struts2-rest-plugin-2.1.6.jar
- xpp3_min-1.1.3.4.O.jar
- xstream-1.2.2.jar
- xwork-2.1.2.jar
Two Struts2 plugins are used:
- Rest Plugin
- Convention Plugin
First of all create at the root of the src
directory the struts.xml
file:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"> <struts> <constant name="struts.convention.package.locators" value="rest"/> <constant name="struts.convention.action.suffix" value="Controller"/> <constant name="struts.convention.action.mapAllMatches" value="true"/> <constant name="struts.convention.default.parent.package" value="rest-default"/> </struts>
Convention plugin makes possible to map classes and methods on automatically generated URLs. It is not required the manual “wiring” usually done in the struts.xml
configuration file that, in this case, contains just a few lines of configuration. The property struts.convention.package.locators
defines the package name where the Convention plugin will look for Struts2 Actions and Controllers, then with struts.convention.action.suffix
it is specified that just the classes with the Controller suffix will be automatically mapped. The REST controller that’s going to be defined in the next step, will have the name MessagesController
and will be contained in the package com.zulutown.struts2.rest
, then it will match both the configurations described above. Given this configuration, the list of the messages in XML format will be accessible through an HTTP GET method on http://localhost:8080/Struts2-Rest/messages.xml
, and to obtain the JSON messages list, it’s enough to call the HTTP GET method on http://localhost:8080/Struts2-Rest/messages.json
. Simple, isn’t? Now, the simple code of the Controller class that will handle the REST requests:
package com.zulutown.struts2.rest; import java.util.Collection; import org.apache.struts2.rest.DefaultHttpHeaders; import org.apache.struts2.rest.HttpHeaders; import com.opensymphony.xwork2.ModelDriven; public class MessagesController implements ModelDriven<Object> { private static final long serialVersionUID = 89268916175477696L; private Message model = new Message(); private String id; private Collection<Message> list; public HttpHeaders create() { MessageService.save(model); return new DefaultHttpHeaders("create"); } public HttpHeaders destroy() { return new DefaultHttpHeaders("destroy"); } public HttpHeaders show() { return new DefaultHttpHeaders("show").disableCaching(); } public HttpHeaders update() { MessageService.save(model); return new DefaultHttpHeaders("update"); } public HttpHeaders index() { list = MessageService.findAll(); return new DefaultHttpHeaders("index").disableCaching();; } public Object getModel() { return (list != null ? list : model); } public String getId() { return id; } public void setId(String id) { if (id != null) { this.model = MessageService.find(id); } this.id = id; } }
In the Struts2 REST plugin, the method name (in the the Controller class) identifies which kind of operation should be executed on specific HTTP requests.
HTTP GET method on http://localhost:8080/Struts2-Rest/messages.xml
calls index()
and provides the list of all the messages (in the model
property of the controller).
The result is:
<list> <com.zulutown.struts2.rest.Message> <id>3</id> <text>rest</text> <author>sam</author> </com.zulutown.struts2.rest.Message> <com.zulutown.struts2.rest.Message> <id>2</id> <text>world</text> <author>ted</author> </com.zulutown.struts2.rest.Message> <com.zulutown.struts2.rest.Message> <id>1</id> <text>hello</text> <author>john</author> </com.zulutown.struts2.rest.Message> </list>
HTTP GET method on http://localhost:8080/Struts2-Rest/messages/2.xml
calls setId("2")
(that loads in the model
property the Message identified by the provided id
) then show()
.
The result is:
<com.zulutown.struts2.rest.Message> <id>2</id> <text>world</text> <author>ted</author> </com.zulutown.struts2.rest.Message>
The previous two methods are easy to call with the browser but, to test the other HTTP methods, it’s better to use the Firefox plugin Poster.
HTTP POST method on http://localhost:8080/Struts2-Rest/messages.xml
injects into model
a new message, then calls create()
that persists it.
In the poster dialog box, the URL must be http://localhost:8080/Struts2-Rest/messages.xml
with POST
action. In Content to Send it’s required to insert the XML entity for the new message (without specifying its id, because it will be automatically generated by MessageService
).
<com.zulutown.struts2.rest.Message> <text>new text</text> <author>new author</author> </com.zulutown.struts2.rest.Message>
HTTP PUT method on http://localhost:8080/Struts2-Rest/messages/2.xml
calls setId("2")
(that causes the loading of the existing message with id=2
in the model
property) then, depending on which fields are specified in the XML, those message properties are modified and finally a call to update()
saves the updated message.
In the poster dialog box, the URL must be http://localhost:8080/Struts2-Rest/messages/2.xml
with PUT
action. In Content to Send it’s required to insert the XML entity for the fields to edit (the unspecified fields will keep the previous values).
<com.zulutown.struts2.rest.Message> <text>updated text</text> </com.zulutown.struts2.rest.Message>
After the POST and PUT calls, the data structure will look like this:
<list> <com.zulutown.struts2.rest.Message> <id>3</id> <text>rest</text> <author>sam</author> </com.zulutown.struts2.rest.Message> <com.zulutown.struts2.rest.Message> <id>2</id> <text>updated text</text> <author>ted</author> </com.zulutown.struts2.rest.Message> <com.zulutown.struts2.rest.Message> <id>1</id> <text>hello</text> <author>john</author> </com.zulutown.struts2.rest.Message> <com.zulutown.struts2.rest.Message> <id>4</id> <text>new text</text> <author>new author</author> </com.zulutown.struts2.rest.Message> </list>
In this tutorial all the URLs refer to xml
content type, but it’s possible to use json
just changing URLs from messages.xml
to messages.json
or messages/2.xml
to messages/2.json
.
I hope this tutorial has given a simple and easy introduction to the REST capabilities of Struts2.1. Please leave your comments and feedback.