2012/01/27

Riding the Dromedary

Before I really get started on this entry there is something I have to get out of my system. There are no camels in Egypt (or most of Africa for that matter). There are dromedaries (camelus dromedarius) in Egypt. The animal with two humps we've come to associate with the word camel (camelus bactrianus) is dominant in Asia (and Hollywood) only. The ship of the Egyptian desert is a dromedary.

Right, a couple of weeks ago the NetKernel newsletter contained a festive Camel recipe. And all banter put aside it looked like an interesting thing to study. At first though - forgive me Peter - I couldn't make heads or tails out of it. Only bumps.

So I set aside a whole day to figure it out and to create my own example. Which I'll explain here.

First we need some definitions. Here is what Apache Camel is :

A powerful open source integration framework based on known Enterprise Integration Patterns with powerful Bean Integration.
Isn't that wonderful? Actually it is about (message) routing and transports. I'm going to assume for a moment that the routing bit (I'll come back to it in the example) is clear and explain about transports. In NetKernel a transport typically sits on the border. The border between NetKernel and the outside. What it does? It mediates. Brings stuff inside the ROC abstraction and takes stuff out. Lets call this stuff messages.

We all know a couple of transports. The FrontEnd HTTPFulcrum and Backend HTTPFulcrum are the prime examples. These are HTTP transports able to take your message (a HTTP request), bring it into the ROC abstraction where we can process it and take a message back (a HTTP response).

It is easy to overlook (I know I did when I started with NetKernel) that there are many other possible transports. Have a look at the Apache Camel Components list for just a small selection (bookmark that page while you are at it). Yes, a small selection, for you can obviously create your own protocols and some of the listed implementations cover more than one protocol.

Another important word. Protocol. Let us give that a definition (from Wikipedia) too :

A communications protocol is a system of digital message formats and rules for exchanging those messages in or between computing systems and in telecommunications.
Translated ... a given transport needs to know what the messages (have to) look like in other to do its work. Sounds logical.

Ok, with the definitions out of the way it is time for some action. My example creates a simple transport that more or less implements the Echo Protocol. Now, I do know how to implement that protocol and if you want to learn more about that (and other network protocols), I advise reading the Unix Network Programming books by Richard Stevens. However, the whole point of Apache Camel is that you do not have to bother with the gory details. For general network protocols you can use the Apache Mina component.

Here is the relevant part of module.xml :

  <rootspace
    name="Echo transport"
    public="true"
    uri="urn:org:practicalnetkernel:tpt:mina:echo">
    
    <prototype>
      <class>org:practicalnetkernel.tpt.mina.endpoint.EchoTransport</class>
      <id>camel.EchoTransport</id>
    </prototype>
    <endpoint>
      <prototype>camel.EchoTransport</prototype>
    </endpoint>
  </rootspace>

You might wonder why we work with a prototype. Well, the answer is given by the existing HTTP Transports. Wouldn't you agree that the Frontend and Backend HTTPFulcrums are actually two instances (with slightly different configurations) of the same HTTP Transport? They are. And the same could apply for all transports. Hence the use of a prototype.

You are going to need libraries. My module's lib directory :
camel-context-2.9.0.jar  -> from Apache Camel download
camel-core-2.9.0.jar     -> from Apache Camel download
camel-mina-2.9.0.jar     -> from Apache Camel download
mina-core-1.1.7.jar      -> from Apache Mina download
slf4j-api-1.6.1.jar      -> from Apache Camel download
slf4j-log4j12-1.6.1.jar  -> from Apache Camel download
log4j-1.2.16.jar         -> from Apache Camel download


Note that there is a more recent version of Apache Mina available. The Apache Camel component (camel-mina) does not know of it yet however.
Remember the components list you had to bookmark ? That list will indicate which third party libraries you need for a given component.

The transport class looks like this :
package org.practicalnetkernel.tpt.mina.endpoint;

import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;

import org.netkernel.layer0.nkf.INKFRequestContext;
import org.netkernel.module.standard.endpoint.StandardTransportImpl;

public class EchoTransport extends StandardTransportImpl {
  public static final String ECHONKTRANSPORT="ECHONKTRANSPORT";
  private CamelContext mCamelContext;  

  protected void postCommission(INKFRequestContext aContext) throws Exception {
    SimpleRegistry lRegistry = new SimpleRegistry();
    lRegistry.put(ECHONKTRANSPORT, this);
    
    mCamelContext = new DefaultCamelContext(lRegistry);
    mCamelContext.addRoutes(
      new RouteBuilder() {
        public void configure() {
          from("mina:udp://0.0.0.0:8181?sync=true")
          .to("class:org.practicalnetkernel.tpt.mina.endpoint.EchoHandler?method=onMessage");
        }
      }
    );
    mCamelContext.start();
  }
  
  protected void preDecommission(INKFRequestContext aContext) throws Exception {
    mCamelContext.stop();
  }
}

The important part here is the route (told you we would discuss that) :
from("mina:udp://0.0.0.0:8181?sync=true")
          .to("class:org.practicalnetkernel.tpt.mina.endpoint.EchoHandler?method=onMessage")

Here lies the real strength of Apache Camel. Without knowing anything about Apache Mina you can figure out that something (our transport) will be listening at port 8181 (normally you'd make that a parameter) and pass the messages to the onMessage method of an EchoHandler class. The sync=true option indicates that the client will wait for a response. You can find all that information on the Apache Camel Mina page.

The handler looks like this :

package org.practicalnetkernel.tpt.mina.endpoint;

import org.netkernel.layer0.nkf.INKFLocale;
import org.netkernel.layer0.nkf.INKFRequestContext;
import org.apache.camel.Message;

public class EchoHandler {
  public void onMessage(Message aMessage) throws Exception {
    // Determine the transport so we can get the NetKernel context.
    EchoTransport transport=(EchoTransport)aMessage.getExchange().getContext().getRegistry().lookup(EchoTransport.ECHONKTRANSPORT);
    INKFRequestContext aContext=transport.getTransportContext();

    // Getting some extra information from the message.
    Object origin = aMessage.getHeader("CamelMinaRemoteAddress");

    // NetKernel processing. Very limited in this case.
    aContext.logRaw(INKFLocale.LEVEL_WARNING, origin.toString() + " - " + aMessage.toString());

    // Setting the transport response
    aMessage.getExchange().getOut().setBody(origin.toString() + " - " + aMessage.toString());
  }
}

And that is it. The incoming message is extended with the address of the caller, send as a warning to the NetKernel log (to prove we came inside the ROC abstraction) and returned to sender.

The original example did exactly the same for HL7, but do you know how to send an HL7 message (to test the transport)? Good on you, I do not. For the people that have the same issue with the above, here is a small  standalone Java program :


import java.net.* ;

/**
 *  A simple datagram client
 *  Shows how to send and receive UDP packets in Java
 *
 *  @author  P. Tellenbach,  http://www.heimetli.ch
 *  @version V1.00
 */
public class DatagramClient {

  private final static int PACKETSIZE = 100 ;
  
  public static void main( String args[] ) {
    // Check the arguments
    if( args.length != 2 ) {
      System.out.println( "usage: java DatagramClient host port" ) ;
      return ;
    }

    DatagramSocket socket = null ;

    try {
      // Convert the arguments first, to ensure that they are valid
      InetAddress host = InetAddress.getByName( args[0] ) ;
      int port         = Integer.parseInt( args[1] ) ;

      // Construct the socket
      socket = new DatagramSocket() ;

      // Construct the datagram packet
      byte [] data = "Hello Server".getBytes() ;
      DatagramPacket packet = new DatagramPacket( data, data.length, host, port ) ;

      // Send it
      socket.send( packet ) ;

      // Set a receive timeout, 2000 milliseconds
      socket.setSoTimeout( 2000 ) ;

      // Prepare the packet for receive
      packet.setData( new byte[PACKETSIZE] ) ;

      // Wait for a response from the server
      socket.receive( packet ) ;

      // Print the response
      System.out.println( new String(packet.getData()) ) ;
    }
    catch( Exception e ) {
      System.out.println( e ) ;
    }
    finally {
      if( socket != null )
        socket.close() ;
    }
  }
}


Use that and see the transport at work. Congratulations, you've just created a new NetKernel transport!


Your challenge for this week is to create your own NetKernel transport with this Apache Camel route :
  from("timer://foo?fixedRate=true&delay=0&period=60000")
  .to("http4://api.twitter.com/1/statuses/public_timeline.xml")
  .to("xslt:resources/stylesheets/gullet.xsl")
  .to("class:org.practicalnetkernel.tpt.tweet.endpoint.PublicTweaterStomach?method=onMessage")
Can you get it to work? What does it do? Send in your solutions to practical<dot>netkernel<at>gmail<dot>com and I'll discuss them in a future post.


While the challenge transport is fun to make (do it, you'll see!), it is also an example of convenience overkill. Once you get used to Apache Camel, you want to ride it all the time. And use it for things that you should do inside the ROC abstraction (where you benefit from all that has to offer). So as always ... use it, do not abuse it. Camels don't actually spit, they throw up and you don't want to be caught in the vomit ...


One last remark to finish this long-winded entry. The animal on the Apache Camel main page ... is a dromedary!