Wednesday, March 5, 2014

Develop RESTful Web Service Using Camel-CXF As OSGi bundle

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.

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:

 
  
   
   
    
   
   
   
  
 

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:

    
      org.apache.camel
      camel-http
      2.10.4
    
   
    
      org.apache.camel
      camel-cxf
      2.10.4
    

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:

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 customers = new HashMap();
    Map orders = new HashMap();

    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);
    }
}

As you can see, it is a POJO. Just above the class definition, we have a line:

 @Path("/customerservice/")
This is the path in URI to invoke the serivce. I will explain in detail how to invoke the service. Another point to make is the path just above each mehtod, such as:
    @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;
    }
The above method definition indicates that when user invoke a HTTP GET method with URL of
     http://localhost:19191/customerservice/customers/123
the 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:




 
  
 

 
  
   
  
 

 

 
  
   
   
    
   
   
   
  
 


The declaration of the RESTful service is as:

 
  
   
  
 

 

As you can see, it is declared with tag of . It is important to note that I had code the URL as http://localhost:19191. In practical application, this will be obtained from registration repository like ZooKeeper, WSRR, etc. Later on, when we invoke the service, this URL will be used.

Deployment to OSGi Container

Deployment is straight forward using the following command:

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:

$ 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 used . Thus the URL is http://localhost:19191. Now in your web browser type the following:
http://localhost:19191/customerservice/customers/123
You should see the output like the following:

    John
    123

Use CURL Test POST Method

Use the following commands to create new customer:

$ 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:

Gary Liu124

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:-)]:


  Gary Liu


Test PUT, GET, DELETE Methods

GET

$ curl http://localhost:19191/customerservice/customers/124
Gary Liu124

PUT

$ 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

$ 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.

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi 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?

    ReplyDelete
  3. Very nice information. You can also check goformule.com for mulesoft tutorials

    ReplyDelete

Anypoint Studio Error: The project is missing Munit lIbrary to run tests

Anypoint Studio 7.9 has a bug. Even if we following the article: https://help.mulesoft.com/s/article/The-project-is-missing-MUnit-libraries-...