Route Template

A Route template is as its name implies a template for a route, which are used to create routes from a set of input parameters. Another way of think is that route templates are parameterized routes.

Route template + input parametersroute

From a route template you can create one or more routes.

Defining route templates in the DSL

Route templates are to be defined in the DSL (just like routes) as shown in the following:

public class MyRouteTemplates extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        // create a route template with the given name
        routeTemplate("myTemplate")
            // here we define the required input parameters (can have default values)
            .templateParameter("name")
            .templateParameter("greeting")
            .templateParameter("myPeriod", "3s")
            // here comes the route in the template
            // notice how we use {{name}} to refer to the template parameters
            // we can also use {{propertyName}} to refer to property placeholders
            .from("timer:{{name}}?period={{myPeriod}}")
                .setBody(simple("{{greeting}} ${body}"))
                .log("${body}");
    }
}

And in XML DSL

<camelContext>
  <routeTemplate id="myTemplate">
    <templateParameter name="name"/>
    <templateParameter name="greeting"/>
    <templateParameter name="myPeriod" defaultValue="3s"/>
    <route>
      <from uri="timer:{{name}}?period={{myPeriod}}"/>
      <setBody><simple>{{greeting}} ${body}</simple></setBody>
      <log message="${body}"/>
    </route>
  </routeTemplate>
</camelContext>

In the examples above there was one route template, but you can define as many as you want. Each template must have an unique id. The template parameters are used for defining the parameters the template accepts. As you can see there are 3 parameters: name, greeting, and myPeriod. The first two parameters are mandatory, where as myPeriod is optional as it has a default value of 3s.

The template parameters are then used in the route as regular property placeholders with the {{ }} syntax. Notice how we use {{name}} and {{greeting}} in the timer endpoint and the simple language.

The route can of course also use regular property placeholders as well. Now imagine there was a property placeholder with the name greeting:

greeting = Davs

Then Camel would normally have used this value Davs when creating the route. However as the route template has defined a template parameter with the same name greeting then a value must be provided when creating routes from the template.

Template parameters take precedence over regular property placeholders.

Creating a route from a route template

To create routes from route templates, then you should use org.apache.camel.builder.TemplatedRouteBuilder.

In the following you can see how this is done with the builder as shown below:

// create two routes from the template
TemplatedRouteBuilder.builder(context, "myTemplate")
    .parameter("name", "one")
    .parameter("greeting", "Hello")
    .add();

TemplatedRouteBuilder.builder(context, "myTemplate")
    .parameter("name", "two")
    .parameter("greeting", "Bonjour")
    .parameter("myPeriod", "5s")
    .add();

The returned value from add is the route id of the new route that was added. However null is returned if the route is not yet created and added, which can happen if CamelContext is not started yet.

If no route id is provided, then Camel will auto assign a route id. In the example above then Camel would assign route ids such as route1, route2 to these routes.

If you want to specify a route id, then use routeId as follows, where the name is myCoolRoute:

TemplatedRouteBuilder.builder(context, "myTemplate")
    .routeId("myCoolRoute")
    .parameter("name", "one
    .parameter("greeting", "hello")
    .parameter("myPeriod", "5s")
    .add();

Binding beans to route template

The route template allows to bind beans which is local scoped and only used as part of creating routes from the template. This allows to use the same template to create multiple routes, where beans are local (private) for each created route.

For example given the following route template where we use templateBean to setup the local bean as shown:

routeTemplate("s3template")
    .templateParameter("region")
    .templateParameter("bucket")
    .templateBean("myClient", S3Client.class, rtc ->
            S3Client.builder().region(rtc.getProperty("region", Region.class)).build();
    )
    .from("direct:s3-store")
     // must refer to the bean with {{myClient}}
    .to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")

The template has two parameters to specify the AWS region and the S3 bucket. To connect to S3 then a software.amazon.awssdk.services.s3.S3Client bean is needed.

To create this bean we specify this with the templateBean DSL where we specify the bean id as myClient. The type of the bean can be specified (S3Client.class) however its optional (can be use if you need to let beans be discovered by type and not name).

This ensures that the code creating the bean is executed later ( when Camel is creating a route from the template), then the code must be specified as a supplier. Because we want during creation of the bean access to template parameters, we use a Camel BeanSupplier which gives access to RouteTemplateContext that is the rtc variable in the code above.

Important: the local bean with id myClient must be referred to using Camel’s property placeholder syntax, eg {{myClient}} in the route template, as shown above with the to endpoint. This is because the local bean must be made unique and Camel will internally re-assign the bean id to use an unique id instead of myClient. And this is done with the help of the property placeholder functionality.

If multiple routes is created from this template, then each of the created routes, have their own S3Client bean created.

Binding beans to route templates from template builder

The TemplatedRouteBuilder also allows to bind local beans (which allows to specify those beans) when creating routes from existing templates.

Suppose the route template below is defined in XML:

<camelContext>
  <routeTemplate id="s3template">
    <templateParameter name="region"/>
    <templateParameter name="bucket"/>
    <route>
      <from uri="direct:s3-store"/>
      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
    </route>
  </routeTemplate>
</camelContext>

The template has no bean bindings for #{{myClient}} which would be required for creating the template.

When creating routes form the template via TemplatedRouteBuilder then you can provide the bean binding if you desire the bean to be local scoped (not shared with others):

TemplatedRouteBuilder.builder(context, "s3template")
    .parameter("region", "US-EAST-1")
    .parameter("bucket", "myBucket")
    .bean("myClient", S3Client.class,
                S3Client.builder()
                    .region(rtc.getProperty("region", Region.class))
                    .build())
    .routeId("mys3route")
    .add();

As you can see the binding is similar to when using templateBean directly in the route template.

Instead of binding the beans from the template builder, you could also create the bean outside the template, and bind it by reference.

final S3Client myClient = S3Client.builder().region(Region.US_EAST_1).build();

TemplatedRouteBuilder.builder(context, "s3template")
    .parameter("region", Region.US_EAST_1)
    .parameter("bucket", "myBucket")
    .bean("myClient", myClient)
    .routeId("mys3route")
    .add();

You should prefer to create the local beans directly from within the template (if possible) because this ensures the route template has this out of the box. Otherwise the bean must be created or provided every time a new route is created from the route template. However the latter gives freedom to create the bean in any other custom way.

Binding beans to route templates using bean types

You can create a local bean by referring to a fully qualified class name which Camel will use to create a new local bean instance. When using this the created bean is created via default constructor of the class.

The bean instance can be configured with properties via getter/setter style. The previous example with creating the AWS S3Client would not support this kind as this uses fluent builder pattern (not getter/setter).

So suppose we have a class as follows

public class MyBar {
    private String name;
    private String address;

    // getter/setter omitted

    public String location() {
        return "The bar " + name + " is located at " + address;
    }
}

Then we can use the MyBar class as a local bean in a route template as follows:

routeTemplate("barTemplate")
    .templateParameter("bar")
    .templateParameter("street")
    .templateBean("myBar")
        .typeClass("com.foo.MyBar")
        .property("name", "{{bar}}")
        .property("address", "{{street}}")
    .end()
    .from("direct:going-out")
    .to("bean:{{myBar}}")

With Java DSL you can also refer to the bean class using type safe way:

    .templateBean("myBar")
        .typeClass(MyBar.class)
        .property("name", "{{bar}}")
        .property("address", "{{street}}")
    .end()

In XML DSL you would do:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <routeTemplate id="myBar">
        <templateParameter name="bar"/>
        <templateParameter name="street"/>
        <templateBean name="myBean" type="#class:com.foo.MyBar">
            <property key="name" value="{{bar}}"/>
            <property key="address" value="{{street}}"/>
        </templateBean>
        <route>
            <from uri="direct:going-out"/>
            <to uri="bean:{{myBar}}"/>
        </route>
    </routeTemplate>
</camelContext>

Binding beans to route templates using scripting languages

You can use scripting languages like groovy, joor, mvel which creates the bean. This allows to define route templates with the scripting language built-in (such as groovy).

For example creating the AWS S3 client can be done as shown in Java (with inlined groovy code):

routeTemplate("s3template")
    .templateParameter("region")
    .templateParameter("bucket")
    .templateBean("myClient", "groovy",
            "software.amazon.awssdk.services.s3.S3Client.S3Client.builder()
            .region(rtc.getProperty("region", Region.class))
            .build()"
    )
    .from("direct:s3-store")
     // must refer to the bean with {{myClient}}
    .to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")

The groovy code can be externalized into a file on the classpath or file system, by using resource: as prefix, such as:

routeTemplate("s3template")
    .templateParameter("region")
    .templateParameter("bucket")
    .templateBean("myClient", "groovy", "resource:classpath:s3bean.groovy")
    .from("direct:s3-store")
     // must refer to the bean with {{myClient}}
    .to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")

Then create the file s3bean.groovy in the classpath root:

import software.amazon.awssdk.services.s3.S3Client
S3Client.builder()
    .region(rtc.getProperty("region", Region.class))
    .build()

The route template in XML DSL can then also use groovy language to create the bean as follows:

<camelContext>
  <routeTemplate id="s3template">
    <templateParameter name="region"/>
    <templateParameter name="bucket"/>
    <templateBean name="myClient" type="groovy">
        <script>
            import software.amazon.awssdk.services.s3.S3Client
            S3Client.builder()
                .region(rtc.getProperty("region", Region.class))
                .build()
        </script>
    </templateBean>
    <route>
      <from uri="direct:s3-store"/>
      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
    </route>
  </routeTemplate>
</camelContext>

Notice how the groovy code can be inlined directly in the route template in XML also. Of course you can also externalize the bean creation code to an external file, by using resource: as prefix:

<camelContext>
  <routeTemplate id="s3template">
    <templateParameter name="region"/>
    <templateParameter name="bucket"/>
    <templateBean name="myClient" type="groovy">
        <script>resource:classpath:s3bean.groovy</script>
    </templateBean>
    <route>
      <from uri="direct:s3-store"/>
      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
    </route>
  </routeTemplate>
</camelContext>

The languages supported are:

Type Description

bean

Calling a method on a Java class to create the bean.

groovy

Using groovy script to create the bean.

joor

Using joor (Java code which are runtime compiled) to create the bean.

mvel

To use Mvel template script to create the bean.

ognl

To use OGNL template script to create the bean.

name

To use a 3rd party language by the given name to create the bean.

Camel will bind RouteTemplateContext as the root object with name rtc when evaluating the script. This means you can get access to all the information from RouteTemplateContext and CamelContext via rtc.

This is what we have done in the scripts in the previous examples where we get hold of a template parameter with:

    rtc.getProperty('region', String.class)

To get access to CamelContext you can do:

    var cn = rtc.getCamelContext().getName()

The most powerful languages to use are groovy and joor. The other languages are limited in flexibility as they are not complete programming languages, but are more suited for templating needs.

It is recommended to either use groovy or joor, if creating the local bean requires coding, and the route templates are not defined using Java code.

The bean language can be used when creating the local bean from an existing Java method (static or not-static method), and the route templates are not defined using Java code.

For example suppose there is a class named com.foo.MyAwsHelper that has a method called createS3Client then you can call this method from the route template in XML DSL:

<camelContext>
  <routeTemplate id="s3template">
    <templateParameter name="region"/>
    <templateParameter name="bucket"/>
    <templateBean name="myClient" type="bean">
        <script>com.foo.MyAwsHelper?method=createS3Client</script>
    </templateBean>
    <route>
      <from uri="direct:s3-store"/>
      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
    </route>
  </routeTemplate>
</camelContext>

The method signature of createS3Client must then have 1 parameter for the RouteTemplateContext as shown:

public static S3Client createS3Client(RouteTemplateContext rtc) {
    return S3Client.builder()
        .region(rtc.getProperty("region", Region.class))
        .build();
}

If you are using pure Java code (both template and creating local bean), then you can create the local bean using Java lambda style as previously documented.

Configuring the type of the created bean

When using scripting languages to create the local bean, then Camel would not know what type (fully qualified class name) then created bean is. The type is therefore unassigned (is set as java.lang.Object) which is a slight limitation.

If you use dependency injection by type, or have the need for looking up the local bean from the Camel Registry via its type, then you must provide this information in the route template with beanType as shown:

<camelContext>
  <routeTemplate id="s3template">
    <templateParameter name="region"/>
    <templateParameter name="bucket"/>
    <templateBean name="myClient" type="bean" beanType="software.amazon.awssdk.services.s3.S3Client">
        <script>com.foo.MyAwsHelper?method=createS3Client</script>
    </templateBean>
    <route>
      <from uri="direct:s3-store"/>
      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
    </route>
  </routeTemplate>
</camelContext>

And in Java DSL you can do:

routeTemplate("s3template")
    .templateParameter("region")
    .templateParameter("bucket")
    .templateBean("myClient", S3Client.class, "bean", "com.foo.MyAwsHelper?method=createS3Client")
    .from("direct:s3-store")
     // must refer to the bean with {{myClient}}
    .to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")

Configuring route templates when creating route

There may be some special situations where you want to be able to do some custom configuration/code when a route is about to be created from a route template. To support this you can use the configure in the route template DSL where you can specify the code to execute as show:

routeTemplate("myTemplate")
    .templateParameter("myTopic")
    .configure((RouteTemplateContext rtc) ->
        // do some custom code here
    )
    .from("direct:to-topic")
    .to("kafka:{{myTopic}}");

JMX management

The route templates can be dumped as XML from the ManagedCamelContextMBean MBean via the dumpRouteTemplatesAsXml operation.

Creating routes from properties file

When using camel-main you can specify the parameters for route templates in application.properties file.

For example given the route template below (from a RouteBuilder class):

routeTemplate("mytemplate")
    .templateParameter("input")
    .templateParameter("result")
    .from("direct:{{input}}")
        .to("mock:{{result}}");

Then we can create two routes from this template by configuring the values in the application.properties file:

camel.route-template[0].template-id=mytemplate
camel.route-template[0].input=foo
camel.route-template[0].result=cheese

camel.route-template[1].template-id=mytemplate
camel.route-template[1].input=bar
camel.route-template[1].result=cheese

Creating routes from custom sources of template parameters

The SPI interface org.apache.camel.spi.RouteTemplateParameterSource can be used to implement custom sources that are used during startup of Camel to create routes via the templates with parameters from the custom source(s).

For example a custom source can be implemented that reads parameters from a shared database that Camel uses during staring by creating routes. This allows to externalize these parameters and as well to easily add more routes with varying parameters.

To let Camel discover custom sources then register the source into the Camel registry.

See Also

See the example camel-example-routetemplate.