Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development • Apache integration software

Implementing a Web Service using Camel in an OSGi bundle

This article describes in detail how to use Camel-CXF to implement a Web Service which processes client requests according to a Camel route. The route will be encapsulated in an OSGi bundle, and deployed on Fabric8 or Red Hat JBoss Fuse 6.1. This is a much more involved example than most of the preceding ones in this series, so it will require the use of Maven as a build tool, as there are simply too many dependencies to manage using simple scripts. However, as with all my Apache integration articles, the principles are explained in considerable detail. No clever IDE tools are required — all the source in the article can be, and was, written using a text editor.

Topics covered by this article include:

  • Describing a Web Service "contract-first" using WSDL
  • Using the Maven cxf-codegen-plugin to generate Java artefacts from the WSDL
  • Specifying Camel-CXF Web Services endpoints in a Blueprint XML file
  • Using Web Services endpoints in a Camel route (at least, one particular way to do so)
  • Setting up Fabric8 to support Camel CXF routes
  • Testing a Web Service by posting a SOAP message using curl
  • Testing a Web Service using SoapUI

This article builds on many of my preceding ones, and I assume a knowledge of OSGi, Fabric8/Fuse, CXF Web Services, Camel, and Maven. Implied in that list of prerequisites is some working knowledge of Web Services in general, and the interpretation of WSDL files in particular. If you are not familiar with the Fabric8 architecture at all, then I would recommend that you read Deploying an OSGi application on Apache Fabric8, from the ground up. If you're not familiar with OSGi either, then before reading that article I would recommend Creating OSGI bundles and services from the ground up using Apache Karaf. If you're not familiar with Camel, then I would recommend working through my article Creating an Apache Camel route in Java using Maven, step-by-step. If you do not have sufficient familiarity with Web Services to be able to interpret a WSDL file, then I would recommend Creating a Web Service using Apache CXF from the ground up. The procedure in this article has been tested on Fedora 20 Linux, although there's no particular reason to think that there is anything platform-specific about it.

Note:
It is getting increasingly difficult to run these complicated integration examples under JDK versions earlier than Java 7. Although JBoss Fuse officially supports JDK 1.6, I would not recommend anything earlier than JDK 1.7 for this sample application.

There isn't space in this article to list all the source code; full source in the form of a Maven project is available from the Download section at the end of the article.

Implementation of the service

XXX

WSDL file

In contract-first Web Service development we describe the Service using a WSDL file, and then typically create Java classes that map onto the various elements of the service — input parameters, return values, and network infrastructure (which I will crudely refer to as 'plumbing' in this article). This latter, class-generation step is usually automated — in this article we'll use the Maven code generator plug-in to do it, although most Web Services development tools provide suitable tooling.

In this example the Web Service consists of one operation called CXFTest. The operation takes one string argument called test and returns one string result. However, because Web Services can accept and return any number of arguments, of a variety of types, we define the argument and return types as sequences. The input value type is referred to as inputCXFTest in the WSDL, and will result in the generation of a class InputCXFTest.java to carry its values. Similarly, the output value type outputCXFTest will map onto a class OutputCXFTest.java.

The relevant section of the WSDL file is as follows (please download the source bundle to get the full file):

 <wsdl:types>
        <xs:schema 
          targetNamespace="http://cxfserver.apacheintegration.kevinboone.net">
            <xs:element name="inputCXFTest">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element type="xs:string" name="test"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="outputCXFTest">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element type="xs:string" name="result"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:schema>
    </wsdl:types>
There's nothing significant about the name result in the output type — a Web Service can return any number of values with any names; but because we're working in Java, it's convenient to adopt Java-like semantics, with a single return result from the operation.

The operation CXFTest itself is defined within a portType section of the WSDL, to map a message representing the input parameters, and a message representing the output parameters, to the operation:

  <wsdl:portType name="CXFTestEndpoint">
        <wsdl:operation name="CXFTest">
            <wsdl:input message="tns:inputCXFTest"/>
            <wsdl:output message="tns:outputCXFTest"/>
        </wsdl:operation>
    </wsdl:portType>

Then we define the endpoint of the Web Service, that is, the specific URL that will be used by clients to invoke the sevice. This endpoint will be be defined by the name CXFTestEndpoint:

   <wsdl:service name="CXFTestEndpointService">
        <wsdl:port name="CXFTestEndpoint" binding="tns:CXFTestBinding">
            "Identifier"><soap:address location               "http://localhost:8181/cxf/camelcxfservertest/testme"/>
        </wsdl:port>
   </wsdl:service>
Note that the prefix /cxf at the start of the service URL is the default for the built-in Fuse CXF implementation. Because the same port, 8181 by default, is used for all HTTP operations including the administration console and the Jolokia RMI-over-HTTP interface, the prefix is necessary to indicate that the request is for the CXF servlet.

The location element does not, in fact, create a service, using the specified URL or any other. This URL is used to advise clients on the best way to contact the service. The service itself, and the all-important URL, will be created by the Camel-CXF endpoint, as discussed below.

Finally, we bind (map) the operation CXFTest to the endpoint tns:CXFTestEndpoint

    <wsdl:binding name="CXFTestBinding" type="tns:CXFTestEndpoint">
        <wsdl:operation name="CXFTest">
            <wsdl:input>
                <soap:body parts="in" use="literal"/>
            </wsdl:input>
            <wsdl:output>
                <soap:body parts="out" use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>

Maven POM file; generating the plumbing

In the article, I've used the Maven code generator plug-in to convert the WSDL file into a set of Java classes. There isn't space to include the whole pom.xml file; the important elements of configuration are wsdl, indicating the location of the WSDL file, and sourceRoot, indicating where the generated sources are to be written.
<configuration>
  ...
  <sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
  <wsdl>${basedir}/src/main/resources/META-INF/wsdl/test.wsdl</wsdl>
</configuration>
Writing the generated files into the generated/src directory keeps them separate from the main (developer-authored) source of the project, and ensures that they will get deleted when you do mvn:clean.

Note that the package name of the generated Java sources matches the targetNamespace definitions in the WSDL file.

The POM file also specifies the Maven coordinates (unique name) of the project:

 <groupId>net.kevinboone.apacheintegration</groupId>
  <artifactId>cxfserver</artifactId>
  <name>A Web Service based on Camel-CXF</name>
  <version>0.0.0.1</version>
This information will be needed in order to install the packaged application into the Fuse/Fabric container from the local Maven repository, as will be explained later.

Blueprint configuration file

In this example, I've configured the Web Service endpoints for Camel in a Blueprint XML file, but the actual route is implemented in Java (see below). This is for no better reason than that I'm more familiar with Java, and the route seemed fiddly to set up using XML alone. No doubt it is possible, however.

The Blueprint file is camel-context.xml in the source bundle; the name is completely irrelevant, as Fuse/Fabric will examine all XML files in the relevant directory (OSGI-INF). The endpoint is defined in the XML as follows:

 <camelcxf:cxfEndpoint id="cXFTest"
     address="/camelcxfservertest/testme"
     endpointName="s:CXFTestEndpoint"
     serviceName="s:CXFTestEndpointService"
     wsdlURL="META-INF/wsdl/test.wsdl"
     serviceClass="net.kevinboone.apacheintegration.cxfserver.CXFTestEndpoint"
     xmlns:s="http://cxfserver.apacheintegration.kevinboone.net"/>
The address element defines the URL for the service, in a way that is interpreted by the Fuse/Fabric Web container. Note that there is no host or port on this URL, and the prefix '/cxf' we had to use in the WSDL file is missing here. This is because the URL we give here is interpreted by the container; the container knows what host and port name are appropriate because these are configured at the container level (the default port will be 8181). The '/cxf' prefix is added by the container so that it can route CXF requests to the CXF servlet — this prefix is not part of the definition of the service itself.

The endpointName and serviceName must match the values configured in the WSDL file. Note that the attributes in the WSDL file were qualified by a namespace:

  <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:tns="http://cxfserver.apacheintegration.kevinboone.net"...>
  ...
  <wsdl:binding name="CXFTestBinding" type="tns:CXFTestEndpoint">
and so we must prefix the same namespace when referring to these WSDL elements from the Camel XML file (xmlns:a=...).

The serviceClass element denotes a class that provides the implementation of the service endpoint — something that extends javax.xml.ws.Service. This class was automatically generated from the WSDL file by the Maven code generator plugin.

The other elements in the Blueprint XML file create an instance of the Java class CXFTestRoutes that will define the actual Camel route in Java:

  <bean id="cXFTestRoutes" 
    class="net.kevinboone.apacheintegration.cxfserver.CXFTestRoutes"/>

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/blueprint">
    <routeBuilder ref="cXFTestRoutes"/>
  </camelContext>
Note that the ref attribute in routeBuilder matches the id of the bean.

The Camel route

The Camel route is defined using the Java domain-specific language (DSL), in the class CXFTestRoutes. The route reads client SOAP messages from the Web Services endpoints, calls a method doProcess (trivally simple in this example) to process the messages, and then writes a response back to the client.

Notice that the input arguments are supplied to Java in the form of a InputCXFTest object (which, in this simple example, has only one method getTest(), which returns the value of the test argument passed by the client). The output must be provided in the form of an OutputCXFTest object, which will be converted by the CXF infrastructure into a suitable SOAP response to return to the client.

The source code is listed in its entirety — I hope that the comments will make it clear what's going on.

public class CXFTestRoutes extends RouteBuilder
  {
  /** This method builds the route. */
  public void configure() throws Exception
    {
    // Read messages from the CXF endpoint cXFTest, 
    // which is defined the Blueprint  XML file
    from("cxf:bean:cXFTest")
      // Ensure that the message body is of the correct input type.
      // InputCXFTest is a class generated by wsdl2java from the 
      //   inputCXFTest type defined in the WSDL file. 
      .convertBodyTo(InputCXFTest.class)
      // Define a processor that wrap the "doService()" method
      //   that does the actual work of this service. The  
      // The processor will call doService with the input object, 
      //   and put the result into an exchange header for later use
      .process (new Processor () {
        public void process (Exchange ex) throws Exception
          {
          InputCXFTest input = (InputCXFTest) ex.getIn().getBody();
          // Do the real work...
          OutputCXFTest response = doService (input);
          // Write the result to an exchange header
          ex.setProperty ("response", response);
          }
        })
       // Set the message body from the result of doProcess().
       // This new body will form the response to the client
        .transform (simple("property.response"));
    }

  /** 
   * This method implements the Service. There would be some logic
   *  in here if this was a real Web Service 
   */
  OutputCXFTest doService (InputCXFTest input)
    {
    // OutputCXFTest is an object generated by wsdl2java from the WSDL
    //  outputCXFTest type. It has only one defined field, called
    //  'result'. The InputCXFTest type has only one field, 'test'.
    //  The getTest() and setResult() methods are auto-generaed by the
    //  tooling.
    OutputCXFTest output = new OutputCXFTest();
    output.setResult ("You said: " + input.getTest());
    return output;
    }
}
Note that the Camel route uses a property on the Exchange to carry the response from the doProcess() method back to the transform method that will form the response to the client. It would be unhelpful to use a simple variable to do this, because such an approach would not be thread-safe.

Building and testing the service

This sample application has been tested on Fabric8 version 1.1.0.Beta6, and Red Hat JBoss Fuse 6.1.0. Most likely it will also work with Apache ServiceMix with the appropriate features installed.

With a clean installation of Fuse 6.1.0, no additional setup should be necessary. However, Fabric8 — and probably an installation of Fuse that has already been configured — will need additional features installed. You might be able to install these features just using features:install at the console prompt, but if the installation is managed as a fabric (and that is the default with Fabric8), then you'll need to apply the necessary features to a profile, and then add that profile to a container. For simple testing purposes, you could add the features to the default profile, and install the test application's bundle directly in the root container. The process for installing the features in the default profile is as follows:

Fabric8:karaf@root> fabric:profile-edit --features war/0.0.0 default
Fabric8:karaf@root> fabric:profile-edit --features cxf/0.0.0 default
Fabric8:karaf@root> fabric:profile-edit --features camel-jaxb/0.0.0 default
Fabric8:karaf@root> fabric:profile-edit --features camel-blueprint/0.0.0 default
Fabric8:karaf@root> fabric:profile-edit --features camel-cxf/0.0.0 default
Note:
Please note that, at the time of writing, at least some releases of Fabric8 later than 1.1.0.Beta6 do not work with this example, for unknown reasons.

With (perhaps) this set-up in place, you can build the service using Maven and deploy it to Fuse/Fabric using the console.

Building the service using Maven

Simply:
$ mvn install
install implies generate-sources, which will generate the Java plumbing corresponding to the WSDL file.

The code will be installed in the local Maven repository which, on Linux, will typically be a directory $HOME/.m2/repository.

Deploying the service to Fabric8/JBoss Fuse

We could deploy the application by specifying the full path of the packaged JAR file but, since it's in the local Maven repository, we can just given the Maven coordinates, like this:
Fabric8:karaf@root> osgi:install -s mvn:net.kevinboone.apacheintegration/cxfserver/0.0.0.1
Bundle ID: 264
Fabric8:karaf@root> start 264
Once a bundle has been installed this way, it is not necessary to reinstall it if there are changes to the code — just do update [bundle_number].

Note that deploying this way installs the code on the root container; in a fabric set-up you probably don't want to do this, except for testing purposes. For production use, it's usually better to add to the application to a profile and add the profile to the relevant container. This process is described in detail in my article Using the Fabric8 Maven plugin to deploy a Camel route to a fabric.

In the even of a successful deployment, you should expect to see in the Fuse/Fabric log file a message like this:

2014-07-31 11:57:29,521 | INFO  | l Console Thread | BlueprintCamelContext
| 124 - org.apache.camel.camel-core - 2.13.1 | Apache Camel 2.13.1
(CamelContext: camel) started in 0.120 seconds

Testing the service using the curl utility

The simplest, basic check that the Web Service is installed correctly is to request its WSDL using a browser; use a URL of the form:
http://localhost:8181/cxf/camelcxfservertest/testme?wsdl
If this works, in a simple case like this, where very little data is exchanged, it is possible to test the service simply py posting a SOAP message to it, and examining the SOAP response. A suitable SOAP message might be:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header />
   <soap:Body>
      <ns2:inputCXFTest
      xmlns:ns2="http://cxfserver.apacheintegration.kevinboone.net">
         <test>Hello, world!</test>
      </ns2:inputCXFTest>
   </soap:Body>
</soap:Envelope>
Of course, you need to know the WSDL format pretty well to form this message, and it's impractical with more complex systems. Assuming that the message is in a text file message.txt, we can post it using a utlity like curl:
$ curl -X POST -T message.txt -H "Content-Type: text/xml" \
  http://localhost:8181/cxf/camelcxfservertest/testme
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:outputCXFTest
xmlns:ns2="http://cxfserver.apacheintegration.kevinboone.net"><result>You said:
Hello, world!</result></ns2:outputCXFTest></soap:Body></soap:Envelope>
The important point here is that the returned SOAP message contains the result of executing the Web Service, which in this simple case is
<result>You said: Hello, world!</result>
Any other kind of response, particularly a SOAP fault message, indicates a problem of some kind.

Testing the service using the SoapUI

SoapUI is a general-purpose Web Services testing tool. Binaries are available for many platforms from the SoapUI Web site. There isn't space here to describe how to install SoapUI, as it differs from one platform to another.

Here is one way to test the Web Service of this article using SoapUI.

1. Within the SoapUI grapuhical user interface, create a new SOAP project (File : New SOAP Project). For the project name, enter some identifier (e.g., cxfservertest). For the initial WSDL, navigate to the file src/main/resources/META-INF/wsdl/test.wsdl in the application source. Ensure "Create Requests" is checked.

2. Under 'Projects' in the left-hand pane, you should see a tree like this:

screenshot


Right-click Request 1 and select 'Show request editor'. This will bring up a SOAP envelope based on the WSDL file, with ? characters where you can enter the input values.

3. Enter some value for the test element in place of the ?.

4. Click the green triangle in the top left of the editor window to run the operation. This should display the SOAP envelope returned by the service:

screenshot


Summary

This article has illustrated one way in which to implement a Web Service using the Camel-CXF component. There are many other ways to achieve the same result; in many cases, it is more appropriate to work with "code-first" development, where the WSDL file and service artefacts are generated from Java interfaces, rather than from WSDL to Java as in this example. In an integration setting, however, I suspect it's more common to have to build a Java implementation from WSDL than the other way around.

Downloads

Source code bundle (Maven project)
Copyright © 1994-2013 Kevin Boone. Updated Jul 31 2014