Friday, March 7, 2014

Develop SOAP Web Service As OSGi Bundle With Camel-CXF

Introduction

This article is a continuation of my post on how to develop a RESTful web service. In this one, I am going explain the procedure to develop, build, and test a SOAP base web service. The deployment environment is OSGi container as Talend ESB 5.3.1. The bundle developed here can be deployed to any OSGi container like JBoss Fuse, ServiceMix, etc. With minor modification, it can also be deployed to Tomcat container.

There are two approaches to develop a SOAP base web service. One is called bottom-up or code first approach. With this approach, develop can write SOAP based service using annotation. The container will be able to expose the service with derived WSDL. The other approach is called Top-Down or contract first approach. Each approach has its pros and cons. The rule-of-thumb for the choice is the following:

  • For large enterprise applications with SOA governance, the Top-Down approach is the choice. It enforces the controlled changes, business oriented, and language neutral.
  • If the organization is small, and you need quick-to-market, the code-first/bottom-up approach is preferred. However, in this case, why don't you choose, RESTful?

The source code for this project has been pushed to gitHub at: https://github.com/garyliu1119/Gary-Liu-ESB. The following is the procedures I push the code to the github:

git add camel-example-cxf-osgi
git commit -m "commit the came-cxf-osg exmaple"
git remote add github https://github.com/garyliu1119/Gary-Liu-ESB.git
git pull github master
git push github master

Verify The Web Service

After download the project, you can compile and deploy the project to an OSGi container by the following commands:

osgi:install -s mvn:org.apache.camel/camel-example-cxf-osgi/2.10.4

watch the log, if there is no errors. it means the Web Service is deployed and started correctly. To verify the web service, we can enter the following URL to a browser:

http://localhost:8040/services/camel-example-cxf-osgi/webservices/incident?wsdl

Note: Different OSGi container may use different port, here I am usng 8040. And the path, services, may vary as well. The port definition is location in the file:

     ${CONTAINER_HOME}/etc/org.ops4j.pax.web.cfg
     org.osgi.service.http.port=8040

And the path is defined in the file:

     ${CONTAINER_HOME}/etcorg.apache.cxf.osgi.cfg"
     org.apache.cxf.servlet.context=/services

The file for WSDL definition is located at:

    src/main/resources/META-INF/wsdl/report_incident.wsdl
Once we verify that the web service is working. We can use SaopUI to test the saop invokation. Here is the sample message you can use:

   
   
      
         111
         2011-03-05
         Christian
         Mueller
         Bla
         
Bla bla
cmueller@apache.org 0049 69 1234567

When you invoke the web service using the above soap xml, you should get the following response:


   
      
         NOK
      
   
    

If you change the givenName to Claus, you will get

     OK

The reason for this is in our route, we look for the giveName in the message body, if it is Claus, we return code of "OK". If you look into the camel route, this will become clear.

pom.xml

The origin of this project is from Apache Camel examples. The code has been distributed with many other ESB continers. The original route is in Java DSL. I converted that to Spring DSL.

There are few interesting points should be noted about this project. The first is the pom.xml. In this file, there is a plugin to generate java code from wsdl, name wsdl2java. You can find the detailed documentation about this utility at

Camel Route

Here is complete list of the file of camel-context.xml




 


 
  
   
  
  
   
   
   
  
 
    
         
         
         
              request-${date:now:yyyy-MM-dd-HHmmssSSS}
         
         
         
         
         
            
                ${body.givenName} == 'Claus'
                    
                         ref:OK
                    
            
            
                    
                         ref:NOK
                    
            
         
    
 


The the above camel-context, you can see that the web service is exposed through a pojo: ReportIncidentEndpoint. It is an java inteface like this:

@WebService(targetNamespace = "http://reportincident.example.camel.apache.org", name = "ReportIncidentEndpoint")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2014-03-07T14:05:18.457-06:00", comments = "Apache CXF 2.6.6")
public interface ReportIncidentEndpoint {

    @WebResult(name = "outputReportIncident", targetNamespace = "http://reportincident.example.camel.apache.org", partName = "out")
    @WebMethod(operationName = "ReportIncident", action = "http://reportincident.example.camel.apache.org/ReportIncident")
    @Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2014-03-07T14:05:18.457-06:00")
    public OutputReportIncident reportIncident(
        @WebParam(partName = "in", name = "inputReportIncident", targetNamespace = "http://reportincident.example.camel.apache.org")
        InputReportIncident in
    );
}

Note: I did not implement the interface method. The CXF library provide default immplementaion. The web service is a cxfEndpoint. The reference for

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.

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