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.
- Create Jersey project in Eclipse.
- Create JSONP RESTful service.
- Create Sencha Touch client application.
- 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; } }
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; } }
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); } }
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>
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.
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
ReplyDeleteThank you for your post!
ReplyDeleteI 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
EVERE: Mapped exception to response: 500 (Internal Server Error)
ReplyDeletejavax.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??
Hi!
ReplyDeleteThis 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
Thanks a ton :)it did... Can i establish connectivity via such a Web Service..?
ReplyDeleteThanks 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??
ReplyDeleteHi! 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.
ReplyDeleteAbout 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.
Sir...I have further doubts.. How to contact you?? I need to send you my workings.. In need of someone's help...
ReplyDeletePlease send me an email: superdafi _at_ o2.pl
ReplyDeleteHi... I need to deploy this as a war file... How can i do that? Can you please help me?
ReplyDelete1. Change your pom.xml: set "packaging" element value to "war"
ReplyDelete2. Invoke: mvn package
Please read more about Maven WAR plugin here: http://maven.apache.org/plugins/maven-war-plugin/usage.html
How can i create the same project in RAD??
ReplyDeleteSorry 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.
ReplyDeleteThank you very much Dafi for your interesting post.
ReplyDeleteBy 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....
Hi,
ReplyDeleteWhat 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'
Hi, can anyone provide create, update, delete example?
ReplyDeleteplease send me example. My mail is ahkoo888@gmail.com