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.

      16 comments:

      1. thanks your article. İt is very attractive. while i follow steps , i got error "A message body writer for Java class pl.barisasik.poc.jsonp3.dto.InvoiceListDto, and Java type class pl.barisasik.poc.jsonp3.dto.InvoiceListDto, and MIME media type application/json was not found" . i dont understand this problem . help . Thanks

        ReplyDelete
      2. Thank you for your post!

        I have come across this issue some time ago. It seems to me that in older versions of Jersey (or JAXB), you had to implement javax.ws.rs.ext.MessageBodyReader/MessageBodyWriter interfaces, and add instances of these objects to the configuration of Jersey server/client. These objects are converting a Java type to a stream.

        Anyway I didn't have to do it for my recent RESTful services. Jersey and JAXB 2.x handled this conversion automatically.

        It seems to me, that this may be library (Jersey or JAXB) version issue. Please try to download and run project from my sources, which I have shared here: https://docs.google.com/leaf?id=0B4PjnyjUtgN8ZjI1YjY2Y2UtZTUwYS00YzY4LThmMjEtMTNjOTg3MGQzMDNm&hl=en_US&authkey=CK7J-PEC

        If it will not help, I will write appropriate MessageBodyWriter. Let me know how did it go. Kind regards, Bart

        ReplyDelete
      3. EVERE: Mapped exception to response: 500 (Internal Server Error)
        javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class pl.bzawadka.poc.jsonp.dto.InvoiceListDto, and Java type class pl.bzawadka.poc.jsonp.dto.InvoiceListDto, and MIME media type application/json was not found
        at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:285)
        at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1437)
        at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1349)
        at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1339)
        at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416)
        at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537)
        at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:699)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at com.sun.grizzly.http.servlet.ServletAdapter$FilterChainImpl.doFilter(ServletAdapter.java:1002)
        at com.sun.grizzly.http.servlet.ServletAdapter$FilterChainImpl.invokeFilterChain(ServletAdapter.java:942)
        at com.sun.grizzly.http.servlet.ServletAdapter.doService(ServletAdapter.java:404)
        at com.sun.grizzly.http.servlet.ServletAdapter.service(ServletAdapter.java:349)
        at com.sun.grizzly.tcp.http11.GrizzlyAdapter.service(GrizzlyAdapter.java:168)
        at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:822)
        at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:719)
        at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1013)
        at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:225)
        at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
        at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
        at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
        at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
        at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
        at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
        at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
        at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
        at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
        at java.lang.Thread.run(Thread.java:722)
        Caused by: com.sun.jersey.api.MessageException: A message body writer for Java class pl.bzawadka.poc.jsonp.dto.InvoiceListDto, and Java type class pl.bzawadka.poc.jsonp.dto.InvoiceListDto, and MIME media type application/json was not found
        ... 27 more
        This is my error... How to clear this??

        ReplyDelete
      4. Hi!

        This is the same problem like adeveloper had. Please read first and second comment and follow this steps:

        1. Download the sources from https://docs.google.com/leaf?id=0B4PjnyjUtgN8ZjI1YjY2Y2UtZTUwYS00YzY4LThmMjEtMTNjOTg3MGQzMDNm&hl=en_US&authkey=CK7J-PEC

        2. Unpack the project and run it from command line:
        mvn clean compile exec:java

        3. Open web browser:
        http://localhost:9998/myresource/invoicejsonp/1234

        Did it help with your problem?
        Cheers, Bart

        ReplyDelete
      5. Thanks a ton :)it did... Can i establish connectivity via such a Web Service..?

        ReplyDelete
      6. Thanks for ur superb post :)I have only one prob... When i type the url it usually takes a very very long time to respond[sometimes it doesn't]... What could be the possible reason??

        ReplyDelete
      7. Hi! I'm happy that it worked for you. You are establishing HTTP connection by calling this URL. You can invoke HTTP GET method from web browser or Sencha Touch app.

        About second comment - I did not notice this problem before. I will try to repeat it on my computer(s). If you will be the first one to find the reason, then please post it here.

        ReplyDelete
      8. Sir...I have further doubts.. How to contact you?? I need to send you my workings.. In need of someone's help...

        ReplyDelete
      9. Please send me an email: superdafi _at_ o2.pl

        ReplyDelete
      10. Hi... I need to deploy this as a war file... How can i do that? Can you please help me?

        ReplyDelete
      11. 1. Change your pom.xml: set "packaging" element value to "war"
        2. Invoke: mvn package

        Please read more about Maven WAR plugin here: http://maven.apache.org/plugins/maven-war-plugin/usage.html

        ReplyDelete
      12. How can i create the same project in RAD??

        ReplyDelete
      13. Sorry mate, I cannot cover every IDE in this article. Eclipse was just an example. RAD is just Eclipse extension, so the steps should be very similar.

        ReplyDelete
      14. Thank you very much Dafi for your interesting post.
        By the way i'm working on a similar project but while folowing your tuto step by step,i was blocked in the generation json file on chrome step.i'm working with embeded maven in eclipse Indigo,the "Got it" service worked very well but once i type the url : http://localhost:9998/myresource/invoicejson/1234 i got nothing on chrome browser but a bad error

        i'm asking for your help,thank you....

        ReplyDelete
      15. Hi,

        What if I get a 404 while testing in the browser with http://localhost:9998/myresource/invoicejsonp/1234 ?
        http://localhost:9998/myresource/ returns the correct message.

        Starting grizzly...
        3 oct. 2012 16:42:06 com.sun.grizzly.Controller logVersion
        INFO: GRIZZLY0001: Starting Grizzly Framework 1.9.31 - 03/10/12 16:42
        Jersey app started with WADL available at http://localhost:9998/application.wadl
        Hit enter to stop it...
        3 oct. 2012 16:42:38 com.sun.jersey.api.core.PackagesResourceConfig init
        INFO: Scanning for root resource and provider classes in the packages:
        wstest.jsonp
        3 oct. 2012 16:42:38 com.sun.jersey.api.core.ScanningResourceConfig logClasses
        INFO: Root resource classes found:
        class wstest.jsonp.MyResource
        3 oct. 2012 16:42:38 com.sun.jersey.api.core.ScanningResourceConfig init
        INFO: No provider classes found.
        3 oct. 2012 16:42:38 com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
        INFO: Initiating Jersey application, version 'Jersey: 1.8 06/24/2011 12:17 PM'

        ReplyDelete
      16. Hi, can anyone provide create, update, delete example?
        please send me example. My mail is ahkoo888@gmail.com

        ReplyDelete