Dynamic Routing – making routing decisions at runtime

When you need to route to a sequence of endpoints, and that list may change based on the response from any of those endpoints, a Dynamic Router can be a good solution. Similar to a Routing Slip, where a list of endpoints to route to is created and executed, the Dynamic Router can alter the next endpoint to route to, based on the results of previous endpoints via a feedback loop.

Dynamic Routing – making routing decisions at runtime

This recipe will show you how to efficiently route a message to endpoints by calling your code to make the routing decisions at runtime.

Getting ready

The Java code for this recipe is located in the org.camelcookbook.routing.dynamicrouter package. The Spring XML files are located under src/main/resources/META-INF/spring and prefixed with dynamicRouter.

How to do it...

There are two steps for a Dynamic Router: create a route consumer and optionally transform the message in preparation for dynamic routing, and call the Dynamic Router Expression (most commonly a Java method through the Method Expression Language).

  1. Create a route and optionally transform the message in preparation for the initial call to the Dynamic Router Expression. The dynamicRouter takes an Expression that will most often be a call to a Java method. It must return null to indicate that routing is finished.

    In the XML DSL, this logic is written as:

    <route>
      <from uri="direct:start"/>
      <dynamicRouter>
        <method ref="myDynamicRouter"
                method="routeMe"/>
      </dynamicRouter>
    </route>

    In the Java DSL, the same thing is expressed as:

    from("direct:start")
      .dynamicRouter(method(MyDynamicRouter.class,
                            "routeMe"));
  2. Create the Dynamic Router. In this example, we're calling a Java method, and we're using Camel's Bean Injection capabilities to pass parts of the exchange to the Java method versus the entire exchange for cleaner code.
    public String routeMe(String body,
      @Properties Map<String, Object> properties) {
      // store a property with the message exchange 
      // this will drive the routing decisions of this 
      // Dynamic Router implementation
      int invoked = 0;
    
      // property will be null on first call
      Object current = properties.get("invoked");
      if (current != null) {
        invoked = Integer.valueOf(current.toString());
      }
      invoked++;
      properties.put("invoked", invoked);
    
      if (invoked == 1) {
        return "mock:a";
      } else if (invoked == 2) {
        return "mock:b,mock:c";
      } else if (invoked == 3) {
        return "direct:other";
      } else if (invoked == 4) {
        return "mock:result";
      }
    
      // no more, so return null
      return null;
    }

How it works...

The Dynamic Router uses an Expression to do the routing. This Expression will be called repeatedly, once after Camel calls the endpoint URIs returned from the last Expression invocation. When routing is finished, the Expression must return null.

In the Dynamic Router EIP, this Expression keeps getting called until this condition is met, so it is very important to test that your Expression ultimately returns null, otherwise your route will loop infinitely.

If the Expression returns a list of endpoint URIs, these will be called in sequence. The Expression may separate the URIs with a delimiter, which, by default, is a comma (,). The separator can be changed using the uriDelimiter attribute of the dynamicRouter DSL statement.

There's more...

As it is so common for the Dynamic Router to be a Java method, we can use Camel's Bean Injection capabilities to put everything into the Java class using Java Annotations. The @DynamicRouter annotation provides the same information to Camel as the dynamicRouter statement within a Camel route. See Chapter 3, Routing to Your Code, for more details on Camel's strong integration with your Java code.

The @Consume annotation tells Camel to create a new route consuming on the provided endpoint URI; there is no need to provide a separate route definition.

Note

You need to instantiate your annotated bean within a Camel supported dependency injection framework, like Spring or OSGi Blueprint, with a defined Camel context for this to work.

@Consume(uri = "direct:dynamicRouteAnnotated")
@DynamicRouter(delimiter = ",")
public String routeMe(String body, 
    @Properties Map<String, Object> properties) {
  // ...
}