Thursday, April 9, 2015

GET and POST with JAX-RS

Environment

  1. Apache Tomcat 7 (dockerized version)
  2. MongoDB 2.6.7 (dockerized version)
  3. JDK 7
  4. Jersey Server 1.9
  5. Spring 4.1.6
  6. Clients: Java, Bash, Python, Javascript



Introduction and Project Description



  • A JAX-RS web application implemented in Jersey
  • Build Automation is handled using Maven
  • Deployment Environment is Apache Tomcat 7 (dockerized version)
  • Consumer can POST or GET a book entity.
    • Data is persisted in MongoDB (dockerized version)
    • The Spring Data Framework (1.7.0) is used to wrap the MongoDB Java Driver (2.11.0)
  • The Spring Framework (4.1.6 BOM) is used for component management
  • Consumers are provided in Java, Python and Javascript (jQuery 2.x)
What's new and different?  Perhaps not much, depending on your background.  I spent most of my early years coding JEE systems using Servlets and Web Services and relational databases with ORM providers to handle the mapping.  Moving to JAX-RS seems cleaner, and MongoDB (in spite of the cumbersome syntax in Java) is far easier for simple persistence.  No ORM mechanism required.

The application component model:

Fig 1: Component Model

The consumer can POST or GET a book entity to the application.

A JSON representation of the book entity looks like this: {"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

The project source code is available at Github (reference below).


GET


The JAX-RS compliant-interface looks like:
@Path("/endpoint")
public class RestEndpoint {

 public static LogManager logger = new LogManager(RestEndpoint.class);

 @GET
 @Path("/get")
 @Produces(MediaType.APPLICATION_JSON)
 public Object get1(@QueryParam("id") Long id) {
   return getController().get(id);
 }

 @GET
 @Path("{id}")
 @Produces(MediaType.APPLICATION_JSON)
 public Object get2(@PathParam("id") Long id) {
  return get1(id);
 }

 // define POST methods ...
}

Using the first GET method, a consumer can query the system like this:
$ curl http://host:8080/blogger/demo/endpoint/123
{"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

and use the second method like this:
$ curl http://host:8080/blogger/demo/endpoint/get?id=123
{"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

Using the requests module in Python, the GET methods look like this:
import requests 

def g1():
 r = requests.get('http://host:8080/blogger/demo/endpoint/123')
 test(r.status_code)

def g2():
 payload = { 'id': '123' }
 headers = {'content-type': 'application/json'}
 r = requests.get('http://host:8080/blogger/demo/endpoint/get', params=payload)
 test(r.status_code)

def test(status_code):
 print 'status_code:', status_code
 assert (200 == status_code or 201 == status_code)

g1()
g2()

Using jQuery, the GET looks like this:
<html>
<head>
<script src="js/jquery-2.1.3.js"></script>
<script>

 var BASE = "http://host:8080/blogger/demo/endpoint/";

 $(document).ready(function() {
  $("#btnSubmitGET").click(function() {
   get($("#txtGET").val());
  });
 });

 /* get data by id */
 function get(id) {
  var url = BASE + id;
  $.getJSON(url, function(data) {
   /* create a list from the results */
   var items = [];
   $.each(data, function(key, val) {
    items.push("<li id='" + key + "'>" + val + "</li>");
   });

   $("<ul/>", {
    "class" : "my-new-list",
    html : items.join("")
   }).appendTo("body");
  });
 }

</script>
</head>

<body>
 <input type="text" name="id" value="9956" id="txtGET" class="id" />
 <input id="btnSubmitGET" type="submit" value="GET" />
</body>
</html>

and the result like this:
Fig 2: Results
Note: The example above is only testing the first GET method, but extending this into the second method (get?id=123) would be trivial.


POST


I've defined three POST methods that look like this:
@POST
@Path("/post")
@Consumes(MediaType.APPLICATION_JSON)
public Response post1(Book book) {
 logger.info("Invoked Post Method 1:\n\t%s", BookAdapter.toString(book));
 return getController().post(book);
}

@POST
@Path("/form")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response post2(@FormParam("id") long id, @FormParam("author") String author, @FormParam("title") String title) {
 logger.info("Invoked Post Method 2:\n\tid = %s, author = %s, title = %s", id, author, title);
 return post1(BookAdapter.transform(id, title, author));
}

@POST
@Path("/{param}")
public Response post3(@PathParam("param") String msg) {
 logger.info("Invoked Post Method 3:\n\t%s", msg);
 try {

  Book book = GsonUtils.toObject(msg, Book.class);
  if (null != book) return post1(book);

 } catch (JsonSyntaxException e) {
  logger.error(e, "Invalid JSON (%s)", msg);
 }

 return ResponseAdapter.postFail("book", msg);
}

In the first case, I can GET and POST a Book entity from a Java consumer, like this:
package com.trimc.blogger.jaxrs;

import static org.junit.Assert.assertEquals;

import javax.ws.rs.core.MediaType;

import org.swtk.common.framework.LogManager;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.trimc.blogger.jaxrs.dto.Book;
import com.trimc.blogger.jaxrs.dto.BookAdapter;

public class WebResourceTester {

 public static LogManager logger = new LogManager(WebResourceTester.class);

 public static void main(String... args) throws Throwable {

  Client client = Client.create();
  Book book = BookAdapter.transform(12345l, "Nutmeg of Consolation", "Patrick O'Brian");

  WebResource webResourcePost = client.resource("http://host:8080/blogger/demo/endpoint/post");
  ClientResponse responsePost = webResourcePost.type(MediaType.APPLICATION_JSON).post(ClientResponse.class, book);

  logger.info("%s", responsePost.getEntity(String.class));

  WebResource webResourceGet = client.resource("http://host:8080/blogger/demo/endpoint/" + book.getId());
  ClientResponse responseGet = webResourceGet.type(MediaType.APPLICATION_JSON).get(ClientResponse.class);

  Book _book = responseGet.getEntity(Book.class);
  assertEquals(book.hashCode(), _book.hashCode());
 }
}

In the form POST method, I can use standard HTML:
<html>
<head>
<body>
 <h3>Demonstrate HTML Form POST</h3>
 <form action="http://192.168.1.7:8080/blogger/demo/endpoint/form"
  method="post">
  <p>
   ID : <input type="text" name="id" value="123" />
  </p>
  <p>
   Author : <input type="text" name="author" value="Patrick OBrian" />
  </p>
  <p>
   Title : <input type="text" name="title" value="The Wine Dark Sea" />
  </p>
  <input type="submit" value="POST" />
 </form>
</body>
</html>


Fig 3: Form POST

and the result demonstrated via RoboMongo:

Fig 4: JSON Results

The third method in jQuery looks like this:
<html>
<head>
<script src="js/jquery-2.1.3.js"></script>
<script>
 var BASE = "http://host:8080/blogger/demo/endpoint/";

 $(document).ready(function() {
  initTextarea();
  $("#btnSubmitPOST").click(function() {
   post($("#txtPOST").val());
  });
 });

 /* initialize the textarea with a payload */
 function initTextarea() {
  var payload = {
   "author" : "Patrick O'Brian",
   "id" : 1234,
   "title" : "The Wine Dark Sea"
  };

  $("#txtPOST").val(JSON.stringify(payload));
 }

 /* post json data */
 function post(id) {
  try {

   var url = BASE + id;
   $.post(url, function(data, status) {
    alert("Data: " + data + "\nStatus: " + status);
   });

  } catch (e) {
   alert(e);
  }
 }
</script>
</head>

<body>
 <h3>Demonstrate JSON POST</h3>
 <textarea name="id" id="txtPOST" class="id" rows="4" cols="50"></textarea>
 <input id="btnSubmitPOST" type="submit" value="POST" />
</body>
</html>

The submission is just JSON in a textarea sent directly to the client:
Fig 5: JSON Post Form

and a demonstration via Robomongo that the data was indeed persisted:
Fig 6: JSON Results

The last two approaches are also shown below in Python:
import requests 

# STATUS CODES:
#  <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
#  200 = OK
#  201 = Created

def post2():
 payload = { 'author' : 'p5-author', 'id' : '9995', 'title' : 'p5-title' }
 headers = {u'content-type': u'application/x-www-form-urlencoded'} 
 r = requests.post("http://host:8080/blogger/demo/endpoint/form", params=payload, headers=headers) 
 test(r.status_code)
 return r.text

def post3():
 r = requests.post( 'http://host:8080/blogger/demo/endpoint/{"author":"this is my content","title":"my title","id":9956}' )
 test(r.status_code)
 return r.text

def test(status_code):
 print 'status_code:', status_code
 assert (200 == status_code or 201 == status_code)

p5();
p6()



References

  1. [GitHub] Source Code for this article [or, Google Drive for a zip file]
  2. Resources:
    1. GitHub] Apache Tomcat 7 (configured for this blog post)
    2. [Docker Register] MongoDB Dockerfile
  3. HOWTO Articles:
    1. [Blog] Setting up Apache Tomcat and Docker
    2. [Blog] Working with MongoDB (Installation and Usage)
    3. [Blog] Using Maven for Build Automation

No comments:

Post a Comment