Appendix A. Simple, the expression language

Camel offers a powerful expression language, which back in the earlier days wasn’t as powerful and was labeled Simple. It has evolved to become much more since then, but don’t worry: it’s still simple to use.

The Simple language is provided out of the box in the camel-core JAR file, which means you don’t have to add any JARs on the classpath to use it.

A.1. Introducing Simple

In a nutshell, the Simple expression language evaluates an expression on the current instance of Exchange that is under processing. The Simple language can be used for both expressions and predicates, which makes it a perfect match to be used in your Camel routes.

For example, the Content-Based Router EIP can leverage the Simple language to define predicates in the when clauses, as shown here:

from("activemq:queue:quotes")
.choice()
.when(simple("${body} contains 'Camel'")).to("activemq:camel")
.when(simple("${header.amount} > 1000")).to("activemq:bigspender")
.otherwise().to("activemq:queue:other");

The equivalent Spring XML example would be as follows:

<route>
<from uri="activemq:queue:quotes"/>
<choice>
<when>
<simple>${body} contains 'Camel'</simple>
<to uri="activemq:camel"/>
</when>
<when>
<simple>${header.amount} > 1000</simple>
<to uri="activemq:bigspender"/>
</when>
<otherwise>
<to uri="activemq:queue:other"/>
</otherwise>
</choice>
</route>

As you can see from the preceding examples, the Simple expression is understandable and similar to other scripting languages. In these examples, Camel will evaluate the expression as a Predicate, which means the result is a boolean, which is either true or false. In the example, you use operators to determine whether the message body contains the word Camel or whether the message header amount is larger than 1000.

That gives you a taste of the Simple language. Let’s look at its syntax.

A.2. Syntax

The Simple language uses ${ } placeholders for dynamic expressions, such as those in the previous examples. You can use multiple ${ } placeholders in the same expression, but nested placeholders aren’t supported.

The following expression is not valid:

"${header.${header.bar}}"

The following is valid:

"Hello ${header.name} thanks for ordering ${body}"

An alternative syntax was introduced in Camel 2.5 to accommodate a clash with Spring’s property placeholder feature. You can now also use $simple{ } placeholders with Simple, such as shown in this example:

"Hello $simple{header.name} thanks for ordering $simple{body}"

These examples use variables such as body and header. The next section covers this.

A.3. Built-in variables

The Simple language provides a number of variables that bind to information in the current Exchange. You’ve already seen the body and header. Table A.1 lists all the variables available.

Table A.1. Variables in the Simple language

Variable

Type

Description

body in.body Object Contains the input message body
out.body Object Contains the output message body
header.XXX in.header.XXX in.headers.XXX Object Contains the input message header XXX
out.header.XXX out.headers.XXX Object Contains the output message header XXX
property.XXX Object Contains the Exchange property XXX
exchangeId String Contains the unique ID of the Exchange
sys.XXX sysenv.XXX String Contains the system environment XXX
exception Object Contains the exception on the Exchange, if any exists
exception.stacktrace String Contains the exception stacktrace on the Exchange, if any exists; requires Camel 2.6 or better
exception.message String Contains the exception message on the Exchange, if any exists
threadName String Contains the name of the current thread; can be used for logging purposes

The variables can easily be used in a Simple expression, as you’ve already seen. Logging the message body can be done using ${body} as shown in the following route snippet:

from("activemq:queue:quotes")
.log("We received ${body}")
.to("activemq:queue:process");

The Simple language also has a set of built-in functions.

A.4. Built-in functions

The Simple language has four functions at your disposal, as listed in table A.2.

Table A.2. Functions provided in the Simple language

Function

Type

Description

bodyAs(type) type Converts the body to the given type. For example, bodyAs(String) or bodyAs(com.foo.MyType). Will return null if the body could not be converted.
mandatoryBodyAs(type) type Converts the body to the given type. Will throw a NoTypeConversionAvailableException if the body could not be converted.
headerAs(key, type) type Converts the header with the given key to the given type. Will return null if the header could not be converted.
bean:beanId[?method] Object Invokes a method on a bean. Camel will look up the bean with the given ID from the Registry and invoke the appropriate method. You can optionally explicitly specify the name of the method to invoke.
date:command:pattern String Formats a date. The command must be either now or header.XXX: now represents the current timestamp, whereas header.XXX will use the header with the key XXX. The pattern is based on the java.text.SimpleDataFormat format.
properties:[locations:]key String Resolves a property with the given key using the Camel Properties component. The Camel Properties component is covered in section 6.1.6 of chapter 6.

For example, to log a formatted date from the message header, you could do as follows:

<route>
<from uri="activemq:queue:quote"/>
<log message="Quote date ${date:header.myDate:yyyy-MM-dd HH:mm:ss}"/>
<to uri="activemq:queue:process"/>
</route>

In this example, the input message is expected to contain a header with the key myDate, which should be of type java.util.Date.

Suppose you need to organize received messages into a directory structure containing the current day’s date as a parent folder. The file producer has direct support for specifying the target filename using the Simple language as shown in bold:

from("activemq:queue:quote")
.to("file:backup/?fileName=${date:now:yyyy-MM-dd}/${exchangeId}.txt")
.to("activemq:queue:process");

Now suppose the file must use a filename generated from a bean. You can use the bean function to achieve this:

from("activemq:queue:quote")
.to("file:backup/?fileName=${bean:uuidBean?method=generate}")
.to("activemq:queue:process");

In this example, Camel will look up the bean with the ID uuidBean from the Registry and invoke the generate method. The output of this method invocation is returned and used as the filename.

The Camel Properties component is used for property placeholders. For example, you could store a property in a file containing a configuration for a big-spender threshold.

big=5000

Then you could refer to the big properties key from the Simple language:

from("activemq:queue:quotes")
.choice()
.when(simple("${header.amount} > ${properties.big}")
.to("activemq:bigspender")
.otherwise()
.to("activemq:queue:other");

The Simple language also has built-in variables when working with the Camel File and FTP components.

A.5. Built-in file variables

Files consumed using the File or FTP components have file-related variables available to the Simple language. Table A.3 lists those variables.

Table A.3. File-related variables available when consuming files

Variable

Type

Description

file:name String Contains the filename (relative to the starting directory)
file:name.ext String Contains the file extension
file:name.noext String Contains the filename without extension (relative to the starting directory)
file:onlyname String Contains the filename without any leading paths
file:onlyname.noext String Contains the filename without extension and leading paths
file:parent String Contains the file parent (the paths leading to the file)
file:path String Contains the file path (including leading paths)
file:absolute Boolean Whether or not the filename is an absolute or relative file path
file:absolute.path String Contains the absolute file path.
file:length file:size Long Contains the file length
file:modified Date Contains the modification date of the file as a java.util.Date type

Among other things, the file variables can be used to log which file has been consumed:

<route>
<from uri="file://inbox"/>
<log message="Picked up ${file:name}"/>
...
</route>

The File and FTP endpoints have options that accept Simple language expressions. For example, the File consumer can be configured to move processed files into a folder you specify. Suppose you must move files into a directory structure organized by dates. This can be done by specifying the expression in the move option, as follows:

<from uri="file://inbox?move=backup/${date:now:yyyyMMdd}/${file:name}"/>

 

Tip

The FTP endpoint supports the same move option as shown here.

 

Another example where the file variables come in handy is if you have to process files differently based on the file extension. For example, suppose you have CSV and XML files:

from("file://inbox")
.choice()
.when(simple("${file:ext} == 'txt'")).to("direct:txt")
.when(simple("${file.ext} == 'xml'")).to("direct:xml")
.otherwise().to("direct:unknown");

 

Note

You can read more about the file variables at the Camel website: http://camel.apache.org/file-language.html.

 

In this appendix, we’ve used the Simple language for predicates. In fact, the previous example determines whether the file is a text file or not. Doing this requires operators.

A.6. Built-in operators

The first example in this appendix implemented the Content-Based Router EIP with the Simple expression language. It used predicates to determine where to route a message, and these predicates use operators. Table A.4 lists all the operators supported in Simple.

Table A.4. Operators provided in the Simple language

Operator

Description

== Tests whether the left side is equal to the right side
> Tests whether the left side is greater than the right side
>= Tests whether the left side is greater than or equal to the right side
< Tests whether the left side is less than the right side
<= Tests whether the left side is less than or equal to the right side
!= Tests whether the left side is not equal to the right side
contains Tests whether the left side contains the String value on the right side
not contains Tests whether the left side doesn’t contain the String value on the right side
in Tests whether the left side is in a set of values specified on the right side; the values must be separated by commas
not in Tests whether the left side is not in a set of values specified on the right side; the values must be separated by commas
range Tests whether the left side is within a range of values defined with the following syntax: from..to
not range Tests whether the left side is not within a range of values defined with the following syntax: from..to
regex Tests whether the left side matches a regular expression pattern defined as a String value on the right side
not regex Tests whether the left side doesn’t match a regular expression pattern defined as a String value on the right side
is Tests whether the left side type is an instance of the value on the right side
not is Tests whether the left side type is not an instance of the value on the right side

The operators require the following syntax:

${leftValue} <OP> rightValue

The value on the left side must be enclosed in a ${ } placeholder. The operator must be separated with a single space on the left and right. The right value can either be a fixed value or another dynamic value enclosed using ${ }.

Let’s look at some examples.

simple("${in.header.foo} == Camel")

Here you test whether the foo header is equal to the String value "Camel". If you want to test for "Camel rocks", you must enclose the String in quotes (because the value contains a space):

simple("${in.header.foo} == 'Camel rocks'")

Camel will automatically type coerce, so you can compare apples to oranges. Camel will regard both as fruit:

simple("${in.header.bar} < 200")

Suppose the bar header is a String with the value "100". Camel will convert this value to the same type as the value on the right side, which is numeric. It will therefore compute 100 < 200, which renders true.

You can use the range operator to test whether a value is in a numeric range.

simple("${in.header.bar} range 100..199")

Both the from and to range values are inclusive. You must define the range exactly as shown.

A regular expression can be used to test a variety of things, such as whether a value is a four-digit value:

simple("${in.header.bar} regex 'd{4}'")

You can also use the built-in functions with the operators. For example, to test whether a given header has today’s date, you can leverage the date function:

simple("${in.header.myDate} == ${date:now:yyyyMMdd}")

 

Tip

You can see more examples in the Camel Simple online documentation: http://camel.apache.org/simple.html.

 

The Simple language also allows you to combine two expressions together.

A.6.1. Combining expressions

The Simple language can combine expressions using the and or or operators.

The syntax for combining two expressions is as follows:

${leftValue} <OP> rightValue <and|or> ${leftValue} <OP> rightValue

Here’s an example using and to group two expressions:

simple("${in.header.bar} < 200 and ${body} contains 'Camel'")

From Camel 2.5 onwards you can combine any number of expressions. In previous releases you could only combine exactly two expressions.

The Simple language also supports an OGNL feature.

A.7. The OGNL feature

Both the Simple language and Bean component support an Object Graph Navigation Language (OGNL) feature when specifying the method name to invoke. OGNL allows you to specify a chain of methods in the expression.

Suppose the message body contains a Customer object that has a getAddress() method. To get the ZIP code of the address, you would simply type the following:

simple("${body.getAddress().getZip()}")

You can use a shorter notation, omitting the get prefix and the parentheses.

simple("${body.address.zip}")

In this example, the ZIP code will be returned. But if the getAddress method returns null, the example would cause a NoSuchMethodException to be thrown by Camel. If you want to avoid this, you can use the null-safe operator ?. as follows:

simple("${body?.address.zip}")

The methods in the OGNL expression can be any method name. For example, to invoke a sayHello method, you would do this:

simple("${body.sayHello}")

Camel uses the bean parameter binding, which we covered in chapter 4. This means that the method signature of sayHello can have parameters that are bound to the current Exchange being routed:

public String sayHello(String body) {
return "Hello " + body;
}

The OGNL feature has specialized support for accessing Map and List types. For example, suppose the getAddress method has a getLines method that returns a List. You could access the lines by their index values, as follows:

simple("${body.address.lines[0]}")
simple("${body.address.lines[1]}")
simple("${body.address.lines[2]}")

If you try to index an element that is out of bounds, an IndexOutOfBoundsException exception is thrown. You can use the null-safe operator to suppress this exception:

simple("${body.address?.lines[2]}")

If you want to access the last element, you can use last as the index value, as shown here:

simple("${body.address.lines[last]}")

The access support for Maps is similar, but you use a key instead of a numeric value as the index. Suppose the message body contains a getType method that returns a Map instance. You could access the gold entry as follows:

simple("${body.type[gold]}")

You could even invoke a method on the gold entry like this:

simple("${body.type[gold].sayHello}")

This concludes our tour of the various features supported by the Camel Simple language. We’ll now take a quick look at how to use the Simple language from custom Java code.

A.8. Using Simple from custom Java code

The Simple language is most often used directly in your Camel routes, in either the Java DSL or a Spring XML file. But it’s also possible to use it from custom Java code.

Here’s an example that uses the Simple language from a Camel Processor.

Listing A.1. Using the Simple language from custom Java code
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.SimpleBuilder;

public class MyProcessor implements Processor {

public void process(Exchange exchange) throws Exception {
SimpleBuilder simple = new SimpleBuilder(
"${body} contains 'Camel'");
if (simple.matches(exchange) {
System.out.println("This is a Camel message");
} else {
System.out.println("This is NOT a Camel message");
}
}
}

As you can see in listing A.1, all it takes is creating an instance of SimpleBuilder, which is capable of evaluating either a predicate or an expression. In the listing, you use the Simple language as a predicate.

To use an expression to say “Hello,” you could do the following:

SimpleBuilder simple = new SimpleBuilder("Hello ${header.name}");
String s = simple.evaluate(exchange, String.class);
System.out.println(s);

Notice how you specify that you want the response back as a String by passing in String.class to the evaluate method.

Listing A.1 uses the Simple language from within a Camel Processor, but you’re free to use it anywhere, such as from a custom bean. Just keep in mind that the Exchange must be passed into the matches method on the SimpleBuilder.

A.9. Summary

This appendix covered the Simple language, which is an expression language provided with Camel.

You saw how well it blends with Camel routes, which makes it easy to define predicates in routes, such as those needed when using the Content-Based Router.

We also looked at how easy it was with the Simple language to access information from the Exchange message by using the built-in variables. You saw that Simple provides functions, such as a date function that formats dates and a bean function that invokes methods on beans.

Finally, we covered OGNL notation, which makes it even easier to access data from nested beans.

All together, the Simple language is a great expression language that should help you with 95 percent of your use cases.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset