Following the article in the previous issue, we bring to your attention the creation of REST web services which interact with persistent data. To start with, we offer you an introduction into JAXB.
Java Architecture for XML Binding is a Java standard which defines the way in which the Java objects are converted into and from XML. It uses a collection of standard mappings and defines an API for the reading and writing of Java objects in and from XML.
The following important annotations are used for JAXB:
@lRootElement(namespace = "namespace")
, defines the root element of the XML arborescence,@XmlType(propOrder = { "field2", "field1",.. })
, allows defining the order in which the fields are written in the XML file,@XmlElement(name = "newName")
, defines the name of the XML element, in case we use a different name from the one in JavaBean.Other annotations will be described directly in the code, as they show up.
The first example is that of a stand alone application. The model of the object, which will be written in and respectively read from the XML file is given by the next piece of code:
@XmlRootElement(name = "message")
// Mandatory
@XmlType(propOrder = { "author", "description"})
// Optional
public class Message {
private String name;
private String author;
@XmlElement(
name = "description")
// Optional
public String getName() {
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
We shall complicate a little bit the previous model, by wrapping it into another model:
@XmlRootElement(namespace = "topic")
public class Topic {
@XmlElementWrapper(name = "topicList")
// the name of the wrapper element
@XmlElement(name = "message")
// the name of the collection element
private ArrayList topicList;
private String topicDescription;
public voidsetTopicList
(ArrayList topicList) {
this.topicList = topicList;
}
public ArrayList getTopicList() {
return topicList;
}
public String getTopicDescription() {
return topicDescription;
}
public void setTopicDescription (String topicDescription) {
this.topicDescription = topicDescription;
}
}
In the test application, we will create the XML file corresponding to the previous model and then we will read it.
public class TopicMain {
private static final String TOPIC_XML = "./topic.xml";
public static void main(String[] args) throws JAXBException, IOException {
ArrayList topicList = new ArrayList();
Message message1 = new Message();
message1.setName("message 1");
message1.setAuthor("Ion Ionescu");
topicList.add(message1);
Message message2 = new Message();
message2.setName("message 2");
message2.setAuthor("Vasile Vasilescu");
topicList.add(message2);
Topic topics = new Topic();
topics.setTopicDescription("about topic");
topics.setTopicList(topicLst);
// create JAXB context and instantiate marshaller
JAXBContext context = JAXBContext.
newInstance(Topic.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// Write to System.out
m.marshal(topic, System.out);
// Write to File
m.marshal(topic, new File(TOPIC_XML));
// get variables from our xml file, created before
System.out.println();
System.out.println("Output: ");
Unmarshaller um = context.createUnmarshaller();
Topic topic2 = (Topic) um.unmarshal(new FileReader(TOPIC_XML));
ArrayList list = topic2.getTopicList();
for (Message message : list) {
System.out.println("Message: " + message.getName() + " from " + message.getAuthor());
}
}
}
JAX-RS supports the automatic creation of XML and JSON files through JAXB. For this, in a Dynamic Web Project we will do the following settings:
This type of services allows us to operate a list of created models, through HTTP calls.
For the beginning, in a Dynamic web project, we will create a model and a singleton, which serves as a provider of data for the model. In order to define the singleton, we used the implementation based on enumeration.
@XmlRootElement
public class Todo {
private String id;
private String summary;
private String description;
public Todo() {
}
public Todo(String id, String summary) {
this.id = id;
this.summary = summary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
public enum TodoDao {
instance;
private Map contentProvider = new HashMap();
private TodoDao() {
Todo todo = new Todo("1", "Learn REST");
todo.setDescription("web service");
contentProvider.put("1", todo);
todo = new Todo("2", "Do something");
todo.setDescription("something else");
contentProvider.put("2", todo);
}
public Map getModel() {
return contentProvider;
}
}
The following classes will be used as REST resources:
public class TodoResource {
@Context
UriInfo uriInfo;
@Context
Request request;
String id;
public TodoResource(UriInfo uriInfo, Request request, String id) {
this.uriInfo = uriInfo;
this.request = request;
this.id = id;
}
@GET
@Produces(MediaType.TEXT_XML)
public Todo getTodoHTML() {
Todo todo = TodoDao.instance.getModel().get(id);
if (todo == null)
throw new RuntimeException("Get: Todo with " + id + " not found");
return todo;
}
@PUT
@Consumes(MediaType.APPLICATION_XML)
public Response putTodo(JAXBElement todo) {
Todo c = todo.getValue();
return putAndGetResponse(c);
}
@DELETE
public void deleteTodo() {
Todo c = TodoDao.instance.getModel().remove(id);
if (c == null) throw new RuntimeException("Delete: Todo with " + id + " not found");
}
private Response putAndGetResponse(Todo todo) {
Response res;
if (TodoDao.instance.getModel().containsKey(
todo.getId())) {
res = Response.noContent().build();
} else {
res = Response.created(uriInfo.getAbsolutePath())
.build();
}
TodoDao.instance.getModel().put(todo.getId(), todo);
return res;
}
}
@Path("/todos")
public class TodosResource {
@Context
UriInfo uriInfo;
@Context
Request request;
@GET
@Produces(MediaType.TEXT_XML)
public List getTodosBrowser() {
List todos = new ArrayList();
todos.addAll(TodoDao.instance.getModel().values());
return todos;
}
@GET
@Path("count")
@Produces(MediaType.TEXT_PLAIN)
public String getCount() {
int count = TodoDao.instance.getModel().size();
return String.valueOf(count);
}
@POST
@Produces(MediaType.TEXT_HTML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newTodo(@FormParam("id") String id,
@FormParam("summary") String summary,
@FormParam("description") String description,
@Context HttpServletResponse servletResponse)
throws IOException {
Todo todo = new Todo(id, summary);
if (description != null) {
todo.setDescription(description);
}
TodoDao.instance.getModel().put(id, todo);
servletResponse.sendRedirect("../create_todo.html");
}
@Path("{todo}")
// Defines that the next path parameter after todos
public TodoResource getTodo(@PathParam("todo") String id) {
return new TodoResource(uriInfo, request, id);
}
}
In the TodosResource class, we have used the annotation @PathParam to define the fact that the id is inserted as a parameter.
Running the application can be done in several ways:
In the browser, on the address: http://localhost:8080/CRUDRest/jaxrs/todos/
By using a html form. We can introduce the data by running the create_toto.html file.
<!DOCTYPE html PUBLIC „-//W3C//DTD HTML 4.01 Transitional//EN” „http://www.w3.org/TR/html4/loose.dtd”>
<html>
<head>
<title>Form to create a new resource</title>
</head>
<body>
<form action=”../CRUDRest/jaxrs/todos” method=”POST”>
<label for=”id”>ID</label>
<input name=”id” /> <br />
<label for=”summary”>Summary</label>
<input name=”summary” /> <br />
Description:
<TEXTAREA NAME=”description” COLS=40 ROWS=6>
</TEXTAREA>
<br />
<input type=”submit” value=”Submit” />
</form>
</body>
</html>
On the address http://localhost:8080/CRUDRest/jaxrs/todos/count, we will count the todo items.
On the address http://localhost:8080/CRUDRest/jaxrs/todos/1, we will visualize the todo having 1 as an id. For an non-existent id, we will have a HTTP error.
We can create the following client application:
public class Main {
public static void main(String[] args) {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(getBaseURI());
Todo todo = new Todo("3", "Blabla");
ClientResponse response = service.path("jaxrs").path("todos").path(todo.getId()).accept(MediaType.APPLICATION_XML).put(ClientResponse.class, todo);
System.out.println(response.getStatus());
// Return code should be 201 == created resource
System.out.println(service.path("jaxrs")
.path("todos").accept(MediaType.TEXT_XML)
.get(String.class));
// Create a Todo
Form form = new Form();
form.add("id", "4");
form.add("summary",
"Demonstration of the client lib for forms");
response = service.path("rest").path("todos")
.type(MediaType.APPLICATION_FORM_URLENCODED)
.post(ClientResponse.class, form);
System.out.println("Form response " + response
.getEntity(String.class));
System.out.println(service.path("jaxrs")
.path("todos")
.accept(MediaType.TEXT_XML).get(String.class));
}
private static URI getBaseURI() {
return UriBuilder.fromUri(
"http://localhost:8080/CRUDRest").build();
}
}
We end this article with the steps to be followed when generating a REST web service:
The JAXB annotations will be added directly to the JAXB entity classes. The services are generated as an EJB session facade.
When we are testing a web service, we need to take the following into consideration:
Steps to take in order to develop a client of the REST web service:
We hope you have enjoyed reading this article and we are looking forward to your questions!