Introduction
The RESTful web services are become more and more popular for its simplicity, easy to develop and maintain, in comparing SOAP/wsdl. There are a lot of debases about whether to use RESTful or SOAP for web service design and enterprise integration. There is really no silver-bullet to solve all the problem with regarding to SOA. However, if you want to develop service over HTTP, which is stateless, resource oriented, then RESTful is the choice. Of course, this article is not about RESTful or SOAP. It is a tutorial on how to develop RESTful web service using Camel-cxf library and deploy the service as OSGi bundle using blueprint.
All the source code for this tutorial is available at https://github.com/garyliu1119/Gary-Liu-ESB/tree/master
Create A Blueprint OSGi Project
I have explained in detail on how to create OSGi bundle using blueprint in my blogger: http://ggl-consulting.blogspot.com/2014/02/osgi-bundle-using-blueprint-dymisfied.html. Here is script to create the project from scratch.
1 2 3 4 5 6 7 | mvn archetype:generate \ -DarchetypeGroupId=org.apache.camel.archetypes \ -DarchetypeArtifactId=camel-archetype-blueprint \ -DarchetypeVersion=2.10.4 \ -DgroupId=com.ggl.esb.osgi.rs \ -DartifactId=osgi.blueprint.rs.demo \ -Dversion=0.0.1-SNAPSHOT |
Run the above script in cygwin/Linux/MacOS, you will see a new folder created: osgi.blueprint.rs.demo. The newly created project comes with a default route which takes the following forms:
1 2 3 4 5 6 7 8 9 10 | <route id= "timerToLog" > <from uri= "timer:foo?period=120000" > <setbody> <method ref= "helloBean" method= "hello" > </method></setbody> <log message= "The message contains ${body}" > <to uri= "mock:result" > </to></log></from></route> </camelcontext> |
Of course, it does not do much. You may deploy this to a karaf container. For testing, I use Talend ESB container. It should work in other karaf container as well. Now, let's move on to develop RESTful web service.
Develop Web Services Using RESTful Protocol
To develop RESTful web service, we need to first update our pom.xml to include camel-cxf artifact. Then, we need to develop our service and related entities.Update pom.xml
In order to work camel-cxf and http, we need add the following two dependencies to our pom.xml file:
1 2 3 4 5 6 7 8 9 10 11 | <dependency> <groupid>org.apache.camel</groupid> <artifactid>camel-http</artifactid> <version>2.10.4</version> </dependency> <dependency> <groupid>org.apache.camel</groupid> <artifactid>camel-cxf</artifactid> <version>2.10.4</version> </dependency> |
Develop RESTful Serivice and Related Classes
I have added few java classes into my project. Here is the snapshot of the project structure.
You may download all the source code from my GitHub at https://github.com/garyliu1119/Gary-Liu-ESB/tree/master. Let me explain the service class named: CustomerService.java. This is the service class exposed as web service. Here is the list of the class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | package com.ggl.esb.osgi.rs; import java.util.HashMap; import java.util.Map; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Path( "/customerservice/" ) public class CustomerService { private static Logger logger = LoggerFactory.getLogger(CustomerService.class); long currentId = 123; Map<long, customer= "" > customers = new HashMap<long, customer= "" >(); Map<long, order= "" > orders = new HashMap<long, order= "" >(); public CustomerService() { init(); } @GET @Path( "/customers/{id}/" ) @Produces( "application/xml" ) public Customer getCustomer(@PathParam( "id" ) String id) { logger.info( "----invoking getCustomer, Customer id is: " + id); long idNumber = Long.parseLong(id); Customer c = customers.get(idNumber); return c; } @PUT @Path( "/customers/" ) public Response updateCustomer(Customer customer) { logger.info( "----invoking updateCustomer, Customer name is: " + customer.getName()); Customer c = customers.get(customer.getId()); Response r; if (c != null ) { customers.put(customer.getId(), customer); r = Response.ok().build(); } else { r = Response.notModified().build(); } return r; } @POST @Path( "/customers/" ) public Response addCustomer(Customer customer) { logger.info( "----invoking addCustomer, Customer name is: " + customer.getName()); customer.setId(++currentId); customers.put(customer.getId(), customer); return Response.ok().type( "application/xml" ).entity(customer).build(); } @DELETE @Path( "/customers/{id}/" ) public Response deleteCustomer(@PathParam( "id" ) String id) { logger.info( "----invoking deleteCustomer, Customer id is: " + id); long idNumber = Long.parseLong(id); Customer c = customers.get(idNumber); Response r; if (c != null ) { r = Response.ok().build(); customers.remove(idNumber); } else { r = Response.notModified().build(); } return r; } @GET @Path( "/orders/{orderId}/" ) public Response getOrder(@PathParam( "orderId" ) String orderId) { logger.info( "----invoking getOrder, Order id is: " + orderId); long idNumber = Long.parseLong(orderId); Order order = orders.get(idNumber); logger.info( "order id: " + order.getId()); return Response.ok().type( "application/xml" ).entity(order).build(); } final void init() { Customer c = new Customer(); c.setName( "John" ); c.setId(123); customers.put(c.getId(), c); Order o = new Order(); o.setDescription( "order 223" ); o.setId(223); orders.put(o.getId(), o); } } </long,></long,></long,></long,> |
As you can see, it is a POJO. Just above the class definition, we have a line:
1 | @Path( "/customerservice/" ) |
1 2 3 4 5 6 7 8 9 | @GET @Path( "/customers/{id}/" ) @Produces( "application/xml" ) public Customer getCustomer(@PathParam( "id" ) String id) { logger.info( "----invoking getCustomer, Customer id is: " + id); long idNumber = Long.parseLong(id); Customer c = customers.get(idNumber); return c; } |
http://localhost:19191/customerservice/customers/123the getorder method will be invoked. Note that the two paths we used customerservice and customers are defined at class level and method level respectively. The id is a variable passed the value of 123.
Service Defination
The Restful service is declared in the blueprint.xml file. Here is the list of the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <!--xml version= "1.0" encoding= "UTF-8" ?--> <blueprint xmlns= "http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:camel= "http://camel.apache.org/schema/blueprint" xmlns:jaxrs= "http://cxf.apache.org/blueprint/jaxrs" xsi:schemalocation= " http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd" > <bean id= "helloBean" class= "com.ggl.esb.osgi.rs.HelloBean" > <property name= "say" value= "Hi from Camel" > </property></bean> <jaxrs:servicebeans> <ref component-id= "customerSvc" > </ref></jaxrs:servicebeans> </jaxrs:server> <bean id= "customerSvc" class= "com.ggl.esb.osgi.rs.CustomerService" > <route id= "timerToLog" > <from uri= "timer:foo?period=120000" > <setbody> <method ref= "helloBean" method= "hello" > </method></setbody> <log message= "The message contains ${body}" > <to uri= "mock:result" > </to></log></from></route> </camelcontext> </bean></blueprint> |
The declaration of the RESTful service is as:
1 2 3 4 5 6 7 8 9 | <jaxrs:servicebeans> <ref component-id= "customerSvc" > </ref></jaxrs:servicebeans> </jaxrs:server> <bean id= "customerSvc" class= "com.ggl.esb.osgi.rs.CustomerService" > </bean> |
As you can see, it is declared with tag of
Deployment to OSGi Container
Deployment is straight forward using the following command:
1 | osgi:install -s mvn:com.ggl.esb.osgi.rs/osgi.blueprint.rs.demo/0.0.1-SNAPSHOT |
Check the log, make sure there is no error. To make sure the service is running using the port 19191, run the following command on cmd or cygwin terminal:
1 2 | $ netstat -an | grep LISTENING | grep 19191 TCP 127.0.0.1:19191 0.0.0.0:0 LISTENING |
Perfect! We know now the RESTful service is runing. Let do some testing.
Test The Service
In this section, we will cover some test cases. The first one will be using the brower to do a simple check. Then, we will use curl utility to invoke POST, PUT, GET, and DELETE methods.
Use Web Browser
The first easy test is via web browser. In our web service defination, I usedhttp://localhost:19191/customerservice/customers/123You should see the output like the following:
1 2 3 4 | <customer> <name>John</name> <id>123</id> </customer> |
Use CURL Test POST Method
Use the following commands to create new customer:
1 | $ curl -X POST -T src/main/resources/client/add_customer.xml -H "Content-Type: text/xml" http: //localhost:19191/customerservice/customers |
On your terminal you should see the something like the following:
1 | <!--xml version= "1.0" encoding= "UTF-8" standalone= "yes" ?--><customer><name>Gary Liu</name><id>124</id></customer> |
This means we added a new record with id of 124, and name of Gary Liu. The source xml file is at src/main/resources/client/add_customer.xml. The content is as the following [probably the simplest xml content:-)]:
1 2 3 | <customer> <name>Gary Liu</name> </customer> |
Test PUT, GET, DELETE Methods
GET
1 2 | $ curl http: //localhost:19191/customerservice/customers/124 <!--xml version= "1.0" encoding= "UTF-8" standalone= "yes" ?--><customer><name>Gary Liu</name><id>124</id></customer> |
PUT
1 | $ curl -X PUT -T src/main/resources/client/update_customer.xml -H "Content-Type: text/xml" http: //localhost:19191/customerservice/customers |
PUT is to update customer with id 123. Now, if we retrieve the customer from browser, we should see the name is Mary now.
DELETE
1 | $ curl -X DELETE http: //localhost:19191/customerservice/customers/123 |
Now, if we try to retrieve the customer with id 123, we should not get anything.
You can also test RESTful service using google REST Console. It is very powerful tool. A lot of profession testers use this tool to test RESTful web service. It is beyond the scope of this tutorial.
Conclution
From this tutorial, we can see how simple a RESTful web service can be written, deployed and test. Most challenges for beginner of web service developer is to setup build and deployment environments. And this is the main purpose of the article.
This comment has been removed by the author.
ReplyDeleteHi Gary, all the images appear to be missing from your blog, I like the example and would like to experiment with it, but hard to follow if cannot see the content. Do you know what happened?
ReplyDeleteVery nice information. You can also check goformule.com for mulesoft tutorials
ReplyDelete