Tuesday, December 28, 2010

Developed a new Routebox component encapsulating camel contexts and routes as camel endpoints

A couple of weeks ago, I contributed an interesting and unique new Camel component called Routebox. Please find the details regarding the Routebox component below.

Before we proceed, A small note about Camel routes

Camel routes are crafted using Java DSL or Spring XML to link URI endpoints representing protocols, technologies and infrastructure components based on well established integration patterns and integration requirements. The routes themselves are discrete, standalone and isolated. Routes are hosted by a camel context which is responsible for lifecycle management and administrative control of hosted routes. Routes are in and of themselves not layered in any way and the same is true of camel contexts.

Now let us identify the need for a Routebox

In integration environments with complex and multi-faceted integration needs along with a wide array of endpoint technologies it is often necessary to craft an integration solution by creating a sense of layering among camel routes effectively organizing them into


Coarse grained routes - routes with Routebox endpoints that facade a collection of inner or lower level routes that are
  • Application specific --> HR routes, Sales routes etc
  • Objective specific --> Shipping routes, Fulfillment routes etc
  • Technology specific --> Database routes, Caching routes, polling routes etc
Fine grained routes - routes that execute a specific business and/or integration pattern.

Requests sent to Routebox endpoints on coarse grained routes can then delegate requests to inner fine grained routes to achieve a specific integration objective via a set of fine grained routes, collect the final inner result, and continue to progress to the next step along the coarse-grained route.

The idea behind a Routebox is to act as a blackbox for a collection of fine grained routes and direct payloads received from the Routebox endpoint to specific inner fine grained routes based on a user defined internal routing strategy or a map that matches headers in payloads to determine the inner routes to which payloads are directed.

This new component facilitates encapsulation and indirection of requests received via a routebox consumer endpoint or producer endpoint on a coarse grained camel route to a set of inner routes declared in an inner camel context.

Further Routebox details

A Routebox endpoint consists of a Routebox URI of the type

routebox:routebox-name?...

It is not necessary for producers do not need to know the inner route endpoint URI and they can simply invoke the Routebox URI endpoint.

The Routebox can be applied on a route as a route consumer or as a route producer.

Route Producer endpoints are of two types
  • Endpoints that direct payloads to a routebox consumer
  • Endpoints that host an inner camel context and direct payloads to inner routes hosted by the camel context

The Routebox URI has a query parameter (sendToConsumer) whose value specifies the type of producer endpoint.

The Routebox itself is currently JVM bound and uses an internal protocol ((SEDA or Direct) to receive incoming requests. The choice of internal protocol is controlled by a query parameter (innerProtocol).

The support for SEDA provides automatic support for asynchronous requests to be sent to the routebox endpoint. Similarly support for the Direct protocol provides support for synchronous requests.

The inner routes in the routebox supports all manner of valid camel endpoints. The inner context is a managed entity controlled by the routebox endpoint. Starting or stopping the routebox endpoint automatically triggers the stoppage of any inner routes and removal of the associated inner camel context for routebox consumers and producers hosting inner contexts and routes.

The dispatching to inner routes is determined by a routebox designer supplied dispatch strategy (a user defined class that implements org.apache.camel.component.routebox.strategy.RouteboxDispatchStrategy). This gives complete control to the routebox designer to direct incoming payloads to different fine grained routes. Alternatively the routbox designer can supply a HashMap (HashMap) consisting of a header value(key) and endpointUri(value). The user can then use the exchange header to send keys that then dictate the fine grained route to which the payload is sent.

Using the Routebox

A Routebox may be created by supplying the Routebox URI with one of the 2 options given below via the Camel Registry.
  • A custom camel context 
  • A List of Routebuilders to be launched in a camel context automatically created by the Routebox

Routebox Example using a List of Routebuilders

Step 1: Create a Registry containing a list of routebuilders and a dispatch strategy. Then add the registry to the Camel Context

protected Context createJndiContext() throws Exception {
        Properties properties = new Properties();

        // jndi.properties is optional
        InputStream in = getClass().getClassLoader().getResourceAsStream("jndi.properties");
        if (in != null) {
            log.debug("Using jndi.properties from classpath root");
            properties.load(in);
        } else {
            // set the default initial factory
            properties.put("java.naming.factory.initial", "org.apache.camel.util.jndi.CamelInitialContextFactory");
        }
        return new InitialContext(new Hashtable(properties));
    }

    protected JndiRegistry createRegistry() throws Exception {
        JndiRegistry registry = new JndiRegistry(createJndiContext());
        
        // Wire the routeDefinitions & dispatchStrategy to the outer camelContext 
        // where the routebox is declared
        List routes = new ArrayList();
        routes.add(new SimpleRouteBuilder());
        registry.bind("registry", createInnerRegistry());
        registry.bind("routes", routes);
        registry.bind("strategy", new SimpleRouteDispatchStrategy());
        
        return registry;
    }

    private JndiRegistry createInnerRegistry() throws Exception {
        JndiRegistry innerRegistry = new JndiRegistry(createJndiContext());
        BookCatalog catalogBean = new BookCatalog();
        innerRegistry.bind("library", catalogBean);        
        
        return innerRegistry;
    }

    protected CamelContext createCamelContext() throws Exception {
        return new DefaultCamelContext(createRegistry());
    }

Step 2: Create a new routebox route to be launched in the camel context. Note that the # entries in the routeboxUri are matched to the created inner registry, routebuilder list and dispatchStrategy in the CamelContext Registry. Note that all routebuilders and associated routes are launched in the routebox created inner context

private String routeboxUri = "routebox:multipleRoutes?innerRegistry=#registry&routeBuilders=#routes&dispatchStrategy=#strategy";

    public void testRouteboxDirectAsyncRequests() throws Exception {
        CamelContext context = createCamelContext();
        template = new DefaultProducerTemplate(context);
        template.start();        
     
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from(routeboxUri)
                    .to("log:Routes operation performed?showAll=true");
            }
        });
        context.start();

        // Now use the ProducerTemplate to send the request to the routebox
        template.requestBodyAndHeader(routeboxUri, book, "ROUTE_DISPATCH_KEY", operation);
    }

Complete Examples
RouteboxDefaultContextAndRouteBuilderTest.java
RouteboxDirectProducerOnlyTest.java
RouteboxDirectTest.java
RouteboxDispatchMapTest.java
RouteboxSedaTest.java
SimpleRouteBuilder.java
RouteboxDemoTestSupport.java
BookCatalog.java
Book.java

Further Details
https://issues.apache.org/jira/browse/CAMEL-3285