How to Implement Custom Expression Language in Camel 3.x

by

Eugene Berman

In this blog post, I’m going to explain the process of implementing a custom expression language support in detail as well as highlight some specifics of the DataSonnet implementation. But first, let me explain how this got started. 

Prelude to a post

Shortly after we released the first version of DataSonnet, our open source data transformation platform, a colleague of mine working on the next version of the PortX platform sent me this Slack message: 

“Hey, can I use DataSonnet as an expression language in Apache Camel?”

And, I keyed:

“No”

But just as my thumb was reaching for ‘Send’, intuition (and curiosity) arrested it. “Well, hmm. Maybe.” So I backspaced over No, responded to my coworker instead with a boiler-plate, time-buying equivocation, opened DuckDuckGo, and searched for…

“how to implement a custom expression language in Camel”

I was expecting a bunch of results with links to tutorials. To my surprise, nothing useful. It was the same with my Google search, nada. At this point, I realized I would need to do a private investigation and get some help from my colleagues as well.

The Solution

So, after a few days of bouncing ideas off coworkers and playing around with a couple of different approaches, we found a solution. Without further adieu, here’s what we came up with. 

Create an empty Maven project

Add the following snippet to the <build><plugins> section:

<plugin>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-package-maven-plugin</artifactId>

   <version>${camel.version}</version>

   <executions>

       <execution>

           <id>generate</id>

           <phase>process-classes</phase>

           <goals>

               <goal>generate-languages-list</goal>

           </goals>

       </execution>

   </executions>

   <configuration>

       <failFast>false</failFast>

   </configuration>

</plugin>

This snippet enables the Camel package plugin.

Next, add the camel-core dependency:

<dependency>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-core</artifactId>

   <version>${camel.version}</version>

</dependency>

The ${camel.version} is the version of the Camel core. I tested this solution with Camel 3.2 but it should work with any 3.x version.

Add other dependencies 

Finally, add any dependencies that your custom expression language may require. In my case, it included dependencies for the DataSonnet mapper as well as some additional helper libraries.

Create resources

Now it’s time to create some resources.
Create the src/main/resources/META-INF/services/org/apache/camel/language folder and create the file named datasonnet there. The content of this file is:

class=com.modus.camel.datasonnet.language.DatasonnetLanguage

Next, create the file language.properties in the src/main/resources/META-INF/services/org/apache/camel folder:


And finally, create a directory:

src/main/resources/org/apache/camel/language/datasonnet and then create a file datasonnet.json:


Create a Java class

First, create a class for our language:


Create an interface

The interface is used for bean injection:


Create classes

This class represents the DataSonnet expression at the runtime:


Note that this implements the GeneratedPropertyConfigurer interface – this is done so that additional parameters controlling the DataSonnet behavior can be passed via exchange properties. You can see the full implementation of this class at https://github.com/modusbox/camel-datasonnet/blob/master/src/main/java/com/modus/camel/datasonnet/language/DatasonnetExpression.java

In addition to this, a model class extending the ExpressionDefinition should be created:


I also wanted to provide convenient datasonnet() functions to be used in route definitions, so I created an abstract DatasonnetRouteBuilder class:


Please visit the camel-datasonnet GitHub repository for complete code and examples of usage. 

Why DataSonnet?

When my coworker originally asked if this was possible, I simultaneously thought to myself, “Why are they asking?” Camel already has many expression languages supported out of the box, including its own Simple language, Groovy, MVEL, and many others. So, why DataSonnet?

For us, the answer is consistency when handling data of different types. 

Normally, one would likely use JsonPath to access the JSON formatted data, XPath for XML, and custom Java beans for CSV. And, if you use Javascript, Groovy, or another scripting language, the code gets cluttered with handling the details of the data format. With DataSonnet, you can use the same language when handling all of these formats and create your own plugins if you need to support other formats.

Usage Example: Dynamic queries

Shortly after finishing the first iteration of this project, I was working on an API that returns a number of results from a database based on the request query parameters. All parameters are optional; for example, a consumer should be able to search by email address, first name only, first and last name, or last name only. So the SQL query must be created dynamically. With Datasonnet expression support, this task was simplified. Here’s how I implemented it:

<setBody>

  <language language=”datasonnet”>

    local headerNames = ["firstName", "lastName", "email"];

    local filteredHeaders = std.foldl(

      function(aggregate, x)

        if (std.objectHas(headers, x) > 0) then

          aggregate + [ x + "' = '" + headers[x] + "'"]

        else aggregate + [],

      headerNames,

      []);


    "SELECT * from members WHERE " + std.join(" AND ", filteredHeaders)

  </language>

</setBody>

<to uri="jdbc:MembersDB"/>

 

Please check out the DataSonnet project, provide feedback, join us in evolving the tool, and participate with our team in the community.

Learn more about DataSonnet:

DataSonnet website

Quick Start Tutorial

Cookbook

Let us know what you think about it in the comments below!

In this blog post, I’m going to explain the process of implementing a custom expression language support in detail as well as highlight some specifics of the DataSonnet implementation. But first, let me explain how this got started. 

Prelude to a post

Shortly after we released the first version of DataSonnet, our open source data transformation platform, a colleague of mine working on the next version of the PortX platform sent me this Slack message: 

“Hey, can I use DataSonnet as an expression language in Apache Camel?”

And, I keyed:

“No”

But just as my thumb was reaching for ‘Send’, intuition (and curiosity) arrested it. “Well, hmm. Maybe.” So I backspaced over No, responded to my coworker instead with a boiler-plate, time-buying equivocation, opened DuckDuckGo, and searched for…

“how to implement a custom expression language in Camel”

I was expecting a bunch of results with links to tutorials. To my surprise, nothing useful. It was the same with my Google search, nada. At this point, I realized I would need to do a private investigation and get some help from my colleagues as well.

The Solution

So, after a few days of bouncing ideas off coworkers and playing around with a couple of different approaches, we found a solution. Without further adieu, here’s what we came up with. 

Create an empty Maven project

Add the following snippet to the <build><plugins> section:

<plugin>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-package-maven-plugin</artifactId>

   <version>${camel.version}</version>

   <executions>

       <execution>

           <id>generate</id>

           <phase>process-classes</phase>

           <goals>

               <goal>generate-languages-list</goal>

           </goals>

       </execution>

   </executions>

   <configuration>

       <failFast>false</failFast>

   </configuration>

</plugin>

This snippet enables the Camel package plugin.

Next, add the camel-core dependency:

<dependency>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-core</artifactId>

   <version>${camel.version}</version>

</dependency>

The ${camel.version} is the version of the Camel core. I tested this solution with Camel 3.2 but it should work with any 3.x version.

Add other dependencies 

Finally, add any dependencies that your custom expression language may require. In my case, it included dependencies for the DataSonnet mapper as well as some additional helper libraries.

Create resources

Now it’s time to create some resources.
Create the src/main/resources/META-INF/services/org/apache/camel/language folder and create the file named datasonnet there. The content of this file is:

class=com.modus.camel.datasonnet.language.DatasonnetLanguage

Next, create the file language.properties in the src/main/resources/META-INF/services/org/apache/camel folder:


And finally, create a directory:

src/main/resources/org/apache/camel/language/datasonnet and then create a file datasonnet.json:


Create a Java class

First, create a class for our language:


Create an interface

The interface is used for bean injection:


Create classes

This class represents the DataSonnet expression at the runtime:


Note that this implements the GeneratedPropertyConfigurer interface – this is done so that additional parameters controlling the DataSonnet behavior can be passed via exchange properties. You can see the full implementation of this class at https://github.com/modusbox/camel-datasonnet/blob/master/src/main/java/com/modus/camel/datasonnet/language/DatasonnetExpression.java

In addition to this, a model class extending the ExpressionDefinition should be created:


I also wanted to provide convenient datasonnet() functions to be used in route definitions, so I created an abstract DatasonnetRouteBuilder class:


Please visit the camel-datasonnet GitHub repository for complete code and examples of usage. 

Why DataSonnet?

When my coworker originally asked if this was possible, I simultaneously thought to myself, “Why are they asking?” Camel already has many expression languages supported out of the box, including its own Simple language, Groovy, MVEL, and many others. So, why DataSonnet?

For us, the answer is consistency when handling data of different types. 

Normally, one would likely use JsonPath to access the JSON formatted data, XPath for XML, and custom Java beans for CSV. And, if you use Javascript, Groovy, or another scripting language, the code gets cluttered with handling the details of the data format. With DataSonnet, you can use the same language when handling all of these formats and create your own plugins if you need to support other formats.

Usage Example: Dynamic queries

Shortly after finishing the first iteration of this project, I was working on an API that returns a number of results from a database based on the request query parameters. All parameters are optional; for example, a consumer should be able to search by email address, first name only, first and last name, or last name only. So the SQL query must be created dynamically. With Datasonnet expression support, this task was simplified. Here’s how I implemented it:

<setBody>

  <language language=”datasonnet”>

    local headerNames = ["firstName", "lastName", "email"];

    local filteredHeaders = std.foldl(

      function(aggregate, x)

        if (std.objectHas(headers, x) > 0) then

          aggregate + [ x + "' = '" + headers[x] + "'"]

        else aggregate + [],

      headerNames,

      []);


    "SELECT * from members WHERE " + std.join(" AND ", filteredHeaders)

  </language>

</setBody>

<to uri="jdbc:MembersDB"/>

 

Please check out the DataSonnet project, provide feedback, join us in evolving the tool, and participate with our team in the community.

Learn more about DataSonnet:

DataSonnet website

Quick Start Tutorial

Cookbook

Let us know what you think about it in the comments below!

In this blog post, I’m going to explain the process of implementing a custom expression language support in detail as well as highlight some specifics of the DataSonnet implementation. But first, let me explain how this got started. 

Prelude to a post

Shortly after we released the first version of DataSonnet, our open source data transformation platform, a colleague of mine working on the next version of the PortX platform sent me this Slack message: 

“Hey, can I use DataSonnet as an expression language in Apache Camel?”

And, I keyed:

“No”

But just as my thumb was reaching for ‘Send’, intuition (and curiosity) arrested it. “Well, hmm. Maybe.” So I backspaced over No, responded to my coworker instead with a boiler-plate, time-buying equivocation, opened DuckDuckGo, and searched for…

“how to implement a custom expression language in Camel”

I was expecting a bunch of results with links to tutorials. To my surprise, nothing useful. It was the same with my Google search, nada. At this point, I realized I would need to do a private investigation and get some help from my colleagues as well.

The Solution

So, after a few days of bouncing ideas off coworkers and playing around with a couple of different approaches, we found a solution. Without further adieu, here’s what we came up with. 

Create an empty Maven project

Add the following snippet to the <build><plugins> section:

<plugin>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-package-maven-plugin</artifactId>

   <version>${camel.version}</version>

   <executions>

       <execution>

           <id>generate</id>

           <phase>process-classes</phase>

           <goals>

               <goal>generate-languages-list</goal>

           </goals>

       </execution>

   </executions>

   <configuration>

       <failFast>false</failFast>

   </configuration>

</plugin>

This snippet enables the Camel package plugin.

Next, add the camel-core dependency:

<dependency>

   <groupId>org.apache.camel</groupId>

   <artifactId>camel-core</artifactId>

   <version>${camel.version}</version>

</dependency>

The ${camel.version} is the version of the Camel core. I tested this solution with Camel 3.2 but it should work with any 3.x version.

Add other dependencies 

Finally, add any dependencies that your custom expression language may require. In my case, it included dependencies for the DataSonnet mapper as well as some additional helper libraries.

Create resources

Now it’s time to create some resources.
Create the src/main/resources/META-INF/services/org/apache/camel/language folder and create the file named datasonnet there. The content of this file is:

class=com.modus.camel.datasonnet.language.DatasonnetLanguage

Next, create the file language.properties in the src/main/resources/META-INF/services/org/apache/camel folder:


And finally, create a directory:

src/main/resources/org/apache/camel/language/datasonnet and then create a file datasonnet.json:


Create a Java class

First, create a class for our language:


Create an interface

The interface is used for bean injection:


Create classes

This class represents the DataSonnet expression at the runtime:


Note that this implements the GeneratedPropertyConfigurer interface – this is done so that additional parameters controlling the DataSonnet behavior can be passed via exchange properties. You can see the full implementation of this class at https://github.com/modusbox/camel-datasonnet/blob/master/src/main/java/com/modus/camel/datasonnet/language/DatasonnetExpression.java

In addition to this, a model class extending the ExpressionDefinition should be created:


I also wanted to provide convenient datasonnet() functions to be used in route definitions, so I created an abstract DatasonnetRouteBuilder class:


Please visit the camel-datasonnet GitHub repository for complete code and examples of usage. 

Why DataSonnet?

When my coworker originally asked if this was possible, I simultaneously thought to myself, “Why are they asking?” Camel already has many expression languages supported out of the box, including its own Simple language, Groovy, MVEL, and many others. So, why DataSonnet?

For us, the answer is consistency when handling data of different types. 

Normally, one would likely use JsonPath to access the JSON formatted data, XPath for XML, and custom Java beans for CSV. And, if you use Javascript, Groovy, or another scripting language, the code gets cluttered with handling the details of the data format. With DataSonnet, you can use the same language when handling all of these formats and create your own plugins if you need to support other formats.

Usage Example: Dynamic queries

Shortly after finishing the first iteration of this project, I was working on an API that returns a number of results from a database based on the request query parameters. All parameters are optional; for example, a consumer should be able to search by email address, first name only, first and last name, or last name only. So the SQL query must be created dynamically. With Datasonnet expression support, this task was simplified. Here’s how I implemented it:

<setBody>

  <language language=”datasonnet”>

    local headerNames = ["firstName", "lastName", "email"];

    local filteredHeaders = std.foldl(

      function(aggregate, x)

        if (std.objectHas(headers, x) > 0) then

          aggregate + [ x + "' = '" + headers[x] + "'"]

        else aggregate + [],

      headerNames,

      []);


    "SELECT * from members WHERE " + std.join(" AND ", filteredHeaders)

  </language>

</setBody>

<to uri="jdbc:MembersDB"/>

 

Please check out the DataSonnet project, provide feedback, join us in evolving the tool, and participate with our team in the community.

Learn more about DataSonnet:

DataSonnet website

Quick Start Tutorial

Cookbook

Let us know what you think about it in the comments below!