Saturday 14 May 2011

How to call Jersey RESTful service from Sencha Touch application

Introduction

This article describes how to create RESTful service using Jersey library (JAX-RS reference implementation) and call this service from Sencha Touch-based application. Sencha can consume JSONP services, where Jersey returns JSON response by default (what is the difference?). To make this two frameworks work together, you must configure Jersey service properly.

Prerequisites
  • JDK 6, 
  • Eclipse IDE with m2eclipse plugin, 
  • Maven 2 (standalone or bundled with Eclipse Helios),
  • Chrome web browser.
    Table of contents
    1. Create Jersey project in Eclipse.
    2. Create JSONP RESTful service.
    3. Create Sencha Touch client application.
    4. Summary.
    Sources

      Create Jersey project in Eclipse

      First, we will create simple Jersey RESTful service.

      Let's generate Jersey application from Maven archetype. To do this, you have to download archetype catalog file. Catalog file will contain location of archetype we want to use (it will be jersey-quickstart-grizzly).

      Download the following file: http://download.java.net/maven/2/archetype-catalog.xml
      and save it in your .m2 directory (on my computer it is C:\Users\bzawadka\.m2\).

      Start your Eclipse IDE and create a new project. Click File > New > Project...
      Select Maven Project and click Next.


      Click Next again (do not skip archetype selection).
      In Catalog dropdown choose Default local. Choose jersey-quickstart-grizzly artifact. Click Next.



      Fill out all Artifact fields (Group Id, Artifact Id, Version, Name) and click Next.


      Now is the time to check whether the project is working properly.
      Hit Ctrl+Shift+X and M to run the project as Maven build.
      Enter clean compile exec:java in Goals textbox. After that, click Apply and Run.

      Our Jersey service should be now running:


      Open your web browser and go to this address: http://localhost:9998/myresource


      Congratulations, your Jersey service is working!
      Now stop the Grizzly server by pressing Enter in Eclipse Console.


      Create JSONP RESTful service

      Simple Jersey service is now running, let's create a new service, which will return JSONP data.
      Create new package - pl.bzawadka.poc.jsonp.dto with two classes - InvoiceDto and InvoiceListDto.


      InvoiceListDto class should look like this:
      package pl.bzawadka.poc.jsonp.dto;
      
      import java.net.URL;
      import java.util.Date;
      
      import javax.xml.bind.annotation.XmlRootElement;
      
      @XmlRootElement(name = "simpleInvoice")
      public class InvoiceDto {
       private int invoiceId;
       private String billNumber;
       private Date invoiceDate;
       private Double invoiceAmount;
       private URL downloadLink;
      
       public InvoiceDto() {
       }
      
       public InvoiceDto(int invoiceId, String billNumber, Date invoiceDate, double invoiceAmount) {
        this.invoiceId = invoiceId;
        this.billNumber = billNumber;
        this.invoiceDate = invoiceDate;
        this.invoiceAmount = invoiceAmount;
       }
      
       public int getInvoiceId() {
        return invoiceId;
       }
      
       public void setInvoiceId(int invoiceId) {
        this.invoiceId = invoiceId;
       }
      
       public String getBillNumber() {
        return billNumber;
       }
      
       public void setBillNumber(String billNumber) {
        this.billNumber = billNumber;
       }
      
       public Date getInvoiceDate() {
        return invoiceDate;
       }
      
       public void setInvoiceDate(Date invoiceDate) {
        this.invoiceDate = invoiceDate;
       }
      
       public Double getInvoiceAmount() {
        return invoiceAmount;
       }
      
       public void setInvoiceAmount(Double invoiceAmount) {
        this.invoiceAmount = invoiceAmount;
       }
      
       public URL getDownloadLink() {
        return downloadLink;
       }
      
       public void setDownloadLink(URL downloadLink) {
        this.downloadLink = downloadLink;
       }
      }
      

      ... where InvoiceListDto class should look like this:
      package pl.bzawadka.poc.jsonp.dto;
      
      import java.util.ArrayList;
      import java.util.List;
      
      import javax.xml.bind.annotation.XmlElement;
      import javax.xml.bind.annotation.XmlRootElement;
      
      @XmlRootElement(name = "invoices")
      public class InvoiceListDto {
      
       private List<InvoiceDto> simpleInvoices = new ArrayList<InvoiceDto>();
      
       public InvoiceListDto() {
       }
      
       public InvoiceListDto(List<InvoiceDto> simpleInvoices) {
        this.simpleInvoices = simpleInvoices;
       }
      
       @XmlElement(name = "invoice")
       public List<InvoiceDto> getSimpleInvoices() {
        return simpleInvoices;
       }
      
       public void setSimpleInvoices(List<InvoiceDto> simpleInvoices) {
        this.simpleInvoices = simpleInvoices;
       }
      }
      

      Now it is high time to update MyResource class. First, we will create new service, which will return standard JSON response. Check out getInvoicesForCustomerJson method!
      App class should look like this:

      package pl.bzawadka.poc.jsonp;
      
      import java.util.ArrayList;
      import java.util.Date;
      import java.util.List;
      
      import javax.ws.rs.*;
      import javax.ws.rs.core.*;
      import pl.bzawadka.poc.jsonp.dto.*;
      
      @Path("/myresource")
      public class MyResource {
      
       @GET
       @Produces("text/plain")
       public String getIt() {
        return "Got it!";
       }
      
       @GET
       @Path("invoicejson/{accountId}")
       @Produces({ MediaType.APPLICATION_JSON })
       public Response getInvoicesForCustomerJson(
         @PathParam(value = "accountId") String accountId) {
      
        InvoiceListDto invoices = generateMockData();
      
        return Response.ok(invoices).build();
       }
      
       private InvoiceListDto generateMockData() {
        List<InvoiceDto> invoices = new ArrayList<InvoiceDto>();
        invoices.add(new InvoiceDto(1, "37897-001", new Date(), 58.92));
        invoices.add(new InvoiceDto(2, "37897-002", new Date(), 293.63));
        invoices.add(new InvoiceDto(3, "37897-003", new Date(), 173.3));
        invoices.add(new InvoiceDto(4, "37897-004", new Date(), 170.71));
        return new InvoiceListDto(invoices);
       }
      }
      

      After you run the build (Alt+Shift+X, M), go to web browser: http://localhost:9998/myresource/invoicejson/1234


      As you can see, this is a "normal" JSON. Unfortunately, this response will not be consumed by Sencha Touch.

      Let's create a new method, which will return JSONP. We have to use a new class - JSONWithPadding. Using this class, the result will be wrapped around a JavaScript callback function, whose name by default is "callback". We have to change media type to application/x-javascript as well.
      Add getInvoicesForCustomerJsonp method to MyResource class:

      @GET
       @Path("invoice/{accountId}")
       @Produces({ "application/x-javascript" })
       public JSONWithPadding getInvoicesForCustomerJsonp(
         @PathParam(value = "accountId") String accountId,
         @QueryParam("callback") String callback) {
      
        InvoiceListDto invoices = generateMockData();
      
        return new JSONWithPadding(invoices, callback);
       }

      Run the build and check new URL: http://localhost:9998/myresource/invoicejsonp/1234


      Now we have JSONP! You can see that JSON object is passed as an argument of callback method.


      Create Sencha Touch client application

      Create a html file and fill it with the following content:
      <!DOCTYPE html>
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <title>Hello Jersey</title>
      
      <script src="lib/touch/sencha-touch.js" type="text/javascript"></script>
      <link href="lib/touch/resources/css/sencha-touch-debug.css" rel="stylesheet" type="text/css" />
      <script>
      
      new Ext.Application({
       launch : function() {
      
        var itemTpl = new Ext.XTemplate([
               '<table>',
                 '<tr><th>Date</th><th>Bill Number</th><th>Amount</th><th></th></tr>',
                '<tpl for=".">',
                 '<tr>',
                  '<td class="date">{invoiceDate}</td>',
                  '<td>{billNumber}</td>',
                  '<td>£{invoiceAmount}</td>',
                 '</tr>',
                '</tpl>',
               '</table>',
              ]);
      
        var makeJSONPRequest = function() {
         panel.update('');
         panel.setLoading(true, true);
      
         Ext.util.JSONP.request({
          url: 'http://localhost:9998/myresource/invoicejsonp/1234',
          callbackKey: 'callback',
          callback: onCallback 
         });
        };
      
        var onCallback = function(result) {
         var data = result.invoice;
         if (data) {
          panel.update(itemTpl.applyTemplate(data));   
          panel.scroller.scrollTo({x: 0, y: 0});                     
         } else {
          alert('There was an error during retrieving data.');
         }
         panel.setLoading(false);
        };
      
        panel = new Ext.Panel({
         fullscreen : true,
         scroll: 'vertical',
         cls: 'card',
         dockedItems: [
          {
           dock : 'top',
           xtype: 'toolbar',
           items: [
            {
             text: 'Load invoices',
             handler: makeJSONPRequest 
            }
           ]
          }
         ]
        });
       }
      });
      
      </script>
      
      </head>
      <body>
      </body>
      </html>
      

      Some comment about this application - in makeJSONPRequest method we are making request to the web service. When browser will receive a response, onCallback method will be invoked. itemTpl defines how invoices will be displayed. You need to download sencha-touch.js and sencha-touch-debug.css files and place them in proper directories.

      Save your html file somewhere on your disk and open this page with Chrome web browser.
      It must be Chrome - only this browser is capable of displaing Sencha Touch application properly (and mobile phones and tablets of course).



      Click on Load invoices button.



      VoilĂ ! Our REST service was invoked, and invoices are being displayed properly. If something went wrong, make sure that web service is running.
      If your page is not as pretty as mine, you can add the following style in the header

      <style type="text/css">
       table {
        border-collapse: collapse;
        font-family: Arial;
       }
       td, th {
        color: #666;
        padding: 5px;
       }
       th {
        color: black;
        font-weight: bold;
       }
      </style>


      Summary

      In this article you have learnt how to create Jersey RESTful service which will return JSONP response body. You were also given an example of Sencha Touch application, which consumes that service. If you are interested in Sencha Touch, which is great mobile framework, visit project page.