Content Based Routing

When you need to route messages based on the content of the message, and/or based on the headers or properties associated with the message, using Camel's Content Based Router EIP is a great way to do that.

Content Based Routing

Content Based Routing and Filtering are very similar. A Content Based Router has multiple predicates, and the contained steps are performed on the first predicate that the message matches, or the optional otherwise statement if none matches (similar to an if () {..} else if () {..} else {..} statement in Java).

Camel's Filter EIP tests against a single predicate, executing the contained processing steps only if the message matches that predicate. The equivalent of a Filter in Java would be a single if statement.

In this recipe, we will see how to use a Content Based Router to route a message to one of the several destinations based on the content (the body) of the message.

Getting ready

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

How to do it...

You need to use the choice DSL statement with one or more when statements, with their associated predicate tests, to see if the message should be routed down their path, and an optional otherwise statement that will catch all the messages that do not match any other when predicates.

  1. Within your route, create a choice statement with at least one when statement. Each when statement must start with a predicate statement followed by one or more processor steps (for example, <to uri="..."/>).

    In the XML DSL, this is written as:

    <choice>
      <when>
        <simple>${body} contains 'Camel'</simple>
        <to uri="mock:camel"/>
        <log message="Camel ${body}"/>
      </when>
      <!-- ... -->
    </choice>

    In the Java DSL, this is expressed as:

    .choice()
      .when()
        .simple("${body} contains 'Camel'")
        .to("mock:camel")
        .log("Camel ${body}")
        //...
    .end()

    Note

    In the Java DSL, you need to end the choice statement explicitly with a call to end() if you want to have further steps run after the Content Based Router is complete. The end() statement is like the closing } of a Java if statement–it lets the routing engine know that you are done with the Content Based Router instructions.

  2. Add an otherwise statement to handle all of the messages that do not match any of your when predicates. An otherwise statement includes one or more steps:

    In the XML DSL, this is written as:

    <choice>
      <!-- ... -->
      <otherwise>
        <to uri="mock:other"/>
        <log message="Other ${body}"/>
      </otherwise>
    </choice>

    In the Java DSL, the same thing is written as follows:

    .choice()
      //...
      .otherwise()
        .to("mock:other")
        .log("Other ${body}")
    .end()

How it works...

A Content Based Router depends on Camel's Predicate capabilities. The preceding example uses Camel's Simple Expression Language. The Simple Expression Language provides a robust set of operators that can work on all of the data contained within the exchange (message, headers, and properties). Each when element requires one predicate, which can be any one of the many built-in Camel Expression Languages, including any one of the POJO (Plain Old Java Object) methods that returns a boolean value.

The message is routed to the first when predicate that matches, in other words, returns true. The message will then be routed to the one or more processor steps specified after the when predicate's expression, by default executing multiple processors as a Pipeline, that is, in sequence. If the message does not match any of the when predicates, it will be routed to the otherwise block, if specified.

After routing the message to the first matching when predicate, or the otherwise statement if there are no matches, it will route to the next processor specified (if any) after the choice statement.

There's more...

If you want to nest a Content Based Router within another Content Based Router, the inner choice statement should be defined in its own route to help with code clarity:

from("direct:start")
  .choice()
    .when()
      .simple("${body} contains '<xml>'")
      .to("direct:processXml")
    .otherwise()
      .to("direct:processPlainText")
  .end();

from("direct:processXml")
  .choice()
    .when()
      .xpath("/order[@units > 100]")
      .to("direct:priorityXmlOrder")
    .otherwise()
      .to("direct:normalXmlOrder")
  .end();

If you are routing within the when or otherwise statements to other EIP patterns, such as Load Balancer or Splitter that specify a Pipeline (sequence) of processors and finish their list of steps with an end() statement, the list of steps should instead be terminated with endChoice() statement. This works around the Java language's inability to maintain the nested context properly, and explicitly returns control from the nested EIP back to the containing choice statement:

.choice()
  .when().simple("${body} contains 'Camel'")
    .to("mock:camel")
 .loadBalance()
 .to("mock:a")
 .to("mock:b")
 .endChoice()
    .log("Camel ${body}")
  .otherwise()
    .to("mock:other")
    .log("Other ${body}")
.end()