Babel Camel Basics

The Babel Camel Basics part exposes which basic statement may be defined.

Messages

In Babel Camel, the base interface which models a message which passes through the route is called Message. A Message contains a payload called body. From Camel point of view, a Babel Message may be understood as the in Message of an Exchange with required methods to read and write the Exchange properties.

For more details, please have a look at the Transformations part.

Basics

A simple route without changing or routing the message. The output will be the same as the input.

import io.xtech.babel.camel.builder.RouteBuilder

val routeDef = new RouteBuilder {
  //sends what is received in the direct
  //  endpoint to the mock endpoint
  from("direct:input").to("mock:output")
}

The producer may set the exchange as InOnly by setting the second argument of to, to false. This would override the default behaviour of the producer.

import io.xtech.babel.camel.builder.RouteBuilder

val routeDef = new RouteBuilder {
  //the mock endpoint is set in InOnly Exchange Pattern
  from("direct:input").to("mock:output", false)
}

The producer may set the exchange as InOut by setting the second argument of to, to true. This would override the default behaviour of the producer.

import io.xtech.babel.camel.builder.RouteBuilder

val routeDef = new RouteBuilder {
  //the mock endpoint is set in InOut Exchange Pattern
  from("direct:input").to("mock:output", true)
}

Route Id

The routeId will give an id to the route, then this id can be used for the monitoring or during testing.

val routeBuilder = new RouteBuilder {
  from("direct:input").
    //the routeId of this route will be "bla"
    routeId("bla").
    //the routeId keyword needs to be at the beginning of the route
    //   (enforced by the Babel DSL)
    to("mock:output")
}

The routeId can not be specified as null nor an empty string.

val route = new RouteBuilder {
  from("direct:input").
    //a routeId may not be empty
    routeId("") must throwA[IllegalArgumentException]
}
val route = new RouteBuilder {
  from("direct:input").
    //a routeId may not be null
    routeId(null) must throwA[IllegalArgumentException]
}

Note

The routeId keyword is member of a set of keywords which should follow directly the from keyword or any keyword of this set.

As

A basic example with type transformation. The keyword as will coerce the type of the message passing within a route to a given type.

val routeDef = new RouteBuilder {
  //message bodies are converted to String if required
  from("direct:input").as[String]
    //the processBody concatenates received String with "4"
    .processBody(_ + "4")
    //sends the concatenated string to the mock endpoint
    .to("mock:output")
}

RequireAs

A basic example with type requirement. The requireAs will type the exchange body for the next keyword and will accept only a message with the given type.

import io.xtech.babel.camel.builder.RouteBuilder

val routeDef = new RouteBuilder {
  //Input Message bodies should be of type String
  //  or would throw an Exception
  from(directConsumer).requireAs[String].
    processBody(_ + "4").to(s"mock:$mockProducer")
}
val producer = camelContext.createProducerTemplate()
producer.sendBody(directConsumer, 123) must throwA[CamelExecutionException]

The requiredAs lets you ensure you will always receive the expected body type. For example, the following may not work.

import io.xtech.babel.camel.builder.RouteBuilder

val routeDef = new RouteBuilder {
  //no input Message may satisfies both type constraints,
  //   thus any message sent would throw an Exception.
  from(directConsumer).requireAs[String].requireAs[Int].
    to(s"mock:$mockProducer")
}
val producer = camelContext.createProducerTemplate()
producer.sendBody(directConsumer, "123") must throwA[CamelExecutionException]

Warning

Camel also provides tools to handle data type at runtime (which may be referred to as “runtime typing”). This may cause the regular typing to modify your data after the requireAs keyword depending on your ecosystem. Unfortunately, there is no way for Babel to prevent such variable behaviour.

Logging

With a log, you can log a defined string (which may use Camel Simple Expression Language) and define:

  • the Log level
  • the Log name
  • a marker for this Log event
import io.xtech.babel.camel.builder.RouteBuilder
import org.apache.camel.LoggingLevel

val routeBuilder = new RouteBuilder {
  from("direct:input")
    //logs to the Trace level message such as "received ID-3423 -> toto"
    .log(LoggingLevel.TRACE, "my.cool.toto", "foo", "received: ${id} -> ${body}")
    //logs to the Info level message such as "ID-3423 -> toto"
    .log(LoggingLevel.INFO, "${id} -> ${body}")
    .to("mock:output")
}

You may also use a function to define what should be logged:

val routeBuilder = new RouteBuilder {
  from("direct:input")
    .log(LoggingLevel.TRACE, "my.cool.toto", "foo", msg => "FOO")
    .log(msg => s"BAR : ${msg.headers}")
    .to("mock:output")
}

Route configuration

Callbacks may be added to a given route in order to manage its lifecycle such as :

  • onInit
  • onStart
  • onSuspend
  • onResume
  • onStop
  • onRemove
var success: Boolean = false
val routeBuilder = new RouteBuilder {
  from("direct:input").
    //As the route is initialing, the success variable is set to true
    onInit(route => success = true).
    to("mock:output")
}

Concerning the exchange lifecycle :

  • onExchangeBegin
  • onExchangeDone
var success: Boolean = false
val routeBuilder = new RouteBuilder {
  from("direct:input").
    //At each time an exchange reach the end of the route,
    //   the success variable is set to true
    onExchangeDone((exchange, route) => success = true).
    to("mock:output")
}

Moreover, you may prevent a route from being started automatically using the autoStartup keyword.

val routeBuilder = new RouteBuilder {
  from("direct:input").routeId("babel").
    //The route is told not starting with the Camel Context
    //   but wait until beeing started especially.
    autoStartup(false).
    to("mock:output")
}

Finally, you may define your own RoutePolicy using the routePolicy keyword such as:

val throttlingInflightRoutePolicy = new ThrottlingInflightRoutePolicy()
val simpleScheduledRoutePolicy = new SimpleScheduledRoutePolicy()
simpleScheduledRoutePolicy.setRouteStartDate(new Date())
val routeBuilder = new RouteBuilder {
  from("direct:input").
    routeId("babel").
    routePolicy(throttlingInflightRoutePolicy, simpleScheduledRoutePolicy).
    to("mock:output")
}

Id

The id will set an id to the previous EIP. This may be useful for visualizing your route or for statisticts.

val routeBuilder = new RouteBuilder {
  from("direct:input").
    routeId(routeId).
    //the id of the processor will be "myProcess"
    processBody(x => x).id("myProcess").
    //the id of the mock endpoint will be "mock"
    to("mock:babel").id("mock")
}

The id may also set the consumer id, using the routId keyword.

  from("direct:input").
    //the id may not be configured for the from
    routeId(routeId).
    to("mock:output")
}

Default ids

Babel provides a way to define eip ids by default (without using id).

To modify this default behavior, you may create your own naming strategy in your RouteBuilder such as:

val routeBuilder = new RouteBuilder {

  override protected implicit val namingStrategy: NamingStrategy = new NamingStrategy {
    var index = 0

    override def name(stepDefinition: StepDefinition): Option[String] = {
      index += 1
      Some(s"id-$index")
    }

    override protected[babel] def newRoute(): Unit = {}
  }

  //the id of the from (and thus the routeId) will be "id-1"
  from("direct:input").
    //the id of the endpoint which allows the subroute will be "id-2"
    processBody(x => x).
    //the id of the mock endpoint will be "id-4"
    to("mock:babel-sub")
}

You may also define your naming strategy depending on the pattern type:

import io.xtech.babel.camel.model.{ LogDefinition, LogMessage }
import io.xtech.babel.fish.NamingStrategy
import io.xtech.babel.fish.model.StepDefinition

val routeDef = new RouteBuilder {

  override protected implicit val namingStrategy = new NamingStrategy {
    override def name(stepDefinition: StepDefinition): Option[String] =
      stepDefinition match {
        //set the id of endpoints to their uri
        case LogDefinition(LogMessage(message)) => Some(s"log:$message")
        //do not modify other EIP ids
        case other                              => None
      }

    override def newRoute(): Unit = {}
  }

  from("direct:input").routeId("babel")
    .process(msg => msg.withBody(_ + "bli"))
    //the id of log EIP will be "log:body ${body}"
    .log("body ${body}")
    //the other pattern id will not be changed by babel
    .to("mock:output")
}