Sunday, April 06, 2014

REST: JAX-RS by Example

REST

Annotation
Description
@PATH(your_path)
Sets the path to base URL + /your_path. The base URL is based on your application name, the servlet and the URL pattern from the web.xmlconfiguration file.
@POST, @GET, @PUT, @DELETE
Indicates that the following method will answer to an HTTP POST/GET/PUT/DELETE request, respectively.
@Produces(MediaType.TEXT_PLAIN[, more-types])
@Produces defines which MIME type is delivered by a method annotated with @GET.
@Consumes(type[, more-types])
@Consumes defines which MIME type is consumed by this method.
@PathParam
Used to inject values from the URL into a method parameter. This way you inject, for example, the ID of a resource into the method to get the correct object.

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

//Plain old Java Object it does not extend as class or implements
//an interface

//The class registers its methods for the HTTP GET request using the @GET annotation.
//Using the @Produces annotation, it defines that it can deliver several MIME types,
//text, XML and HTML.

//The browser requests per default the HTML MIME type.

//Sets the path to base URL + /hello
@Path("/hello")
public class Hello {
// This method is called if TEXT_PLAIN is request
 @GET
 @Produces(MediaType.TEXT_PLAIN)
 public String sayPlainTextHello() {
   return "Hello Jersey";
 }

 // This method is called if XML is request
 @GET
 @Produces(MediaType.TEXT_XML)
 public String sayXMLHello() {
   return "<?xml version=\"1.0\"?>" + "<hello> Hello Jersey" + "</hello>";
 }

 // This method is called if HTML is request
 @GET
 @Produces(MediaType.TEXT_HTML)
 public String sayHtmlHello() {
   return "<html> " + "<title>" + "Hello Jersey" + "</title>"
       + "<body><h1>" + "Hello Jersey" + "</body></h1>" + "</html> ";
 }

}

import static org.junit.Assert.assertEquals;

import java.net.URI;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;

import org.junit.BeforeClass;
import org.junit.Test;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;

public class HelloTest {

          private static WebResource service;

          @BeforeClass
          public static void setUpBeforeClass() throws Exception {
                   ClientConfig config = new DefaultClientConfig();
                   Client client = Client.create(config);
                   service = client.resource(getBaseURI());
          }

          @Test
          public void testSayPlainTextHello() {
                   String msg = service.path("rest").path("hello")
                                      .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class)
                                      .toString();
                   System.out.println(msg);
                   assertEquals(true, msg.contains("200"));
          }

          private static URI getBaseURI() {
                   return UriBuilder.fromUri("http://localhost:8080/rest").build();
          }

}

A more complete example will be a todo service:
package rest.todo;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
// JAX-RS supports an automatic mapping from JAXB annotated class to XML and
// JSON
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 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 String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

}

package rest.todo;

import java.util.HashMap;
import java.util.Map;

public enum TodoDao {
instance;

private Map contentProvider = new HashMap();

private TodoDao() {

Todo todo = new Todo("1", "Learn REST");
todo.setDescription("Read http://www.vogella.com/tutorials/REST/article.html");
contentProvider.put("1", todo);
todo = new Todo("2", "Do something");
todo.setDescription("Read complete http://www.vogella.com");
contentProvider.put("2", todo);

}

public Map getModel() {
return contentProvider;
}

}

package rest.todo;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBElement;

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;
}

// Application integration
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Todo getTodo() {
Todo todo = TodoDao.instance.getModel().get(id);
if (todo == null)
throw new RuntimeException("Get: Todo with " + id + " not found");
return todo;
}

// for the browser
@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;
}
}

package rest.todo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;

//Will map the resource to the URL todos
@Path("/todos")
public class TodosResource {
// Allows to insert contextual objects into the class,
// e.g. ServletContext, Request, Response, UriInfo
@Context
UriInfo uriInfo;
@Context
Request request;

// Return the list of todos to the user in the browser
@GET
@Produces(MediaType.TEXT_XML)
public List getTodosBrowser() {
List todos = new ArrayList();
todos.addAll(TodoDao.instance.getModel().values());
return todos;
}

// Return the list of todos for applications
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public List getTodos() {
List todos = new ArrayList();
todos.addAll(TodoDao.instance.getModel().values());
return todos;
}

// to get the total number of records
@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");
}

// Defines that the next path parameter after todos is
// treated as a parameter and passed to the TodoResources
// Allows to type http://localhost:8080/de.vogella.jersey.todo/rest/todos/1
// 1 will be treaded as parameter todo and passed to TodoResource
@Path("{todo}")
public TodoResource getTodo(@PathParam("todo") String id) {
return new TodoResource(uriInfo, request, id);
}
}

And finally a test client:

package rest.todo;

import java.net.URI;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;

import org.junit.BeforeClass;
import org.junit.Test;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;

public class TodoTest {

private static WebResource service;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
service = client.resource(getBaseURI());
}

@Test
public void test() {
// Get XML
System.out.println(service.path("rest").path("todo")
.accept(MediaType.TEXT_XML).get(String.class));
// Get XML for application
System.out.println(service.path("rest").path("todo")
.accept(MediaType.APPLICATION_JSON).get(String.class));
// Get JSON for application
System.out.println(service.path("rest").path("todo")
.accept(MediaType.APPLICATION_XML).get(String.class));
}

private static URI getBaseURI() {
return UriBuilder.fromUri(
"http://localhost:8080/rest").build();
}

}


No comments:

Book notes: Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems, by Martin Kleppmann

My notes from the excellent book on how software has evolved to handle data from hierarchical databases to the NoSQL -  https://www.goodrea...