Simple - Advanced Features
Init Blocks
Starting from Camel 4.18 you can in the top of your Simple expressions declare an initialization block that are used to define a set of local variables that are pre-computed, and can be used in the following Simple expression. This allows to reuse variables, and also avoid making the simple expression complicated when having inlined functions, and in general make the simple expression easier to maintain and understand.
| Init Blocks is not supported with CSimple language. |
The init block declaration
$init{ … }init$
may be changed in the future.
|
The init block is declared as follows:
$init{
// init block
// goes here
// ...
}init$
// here is the regular simple expression
Notice how the block uses the
$init{
…
}init$
markers to indicate the start and end of the block.
Inside the init block, then you can assign local variables in the syntax
$key := <statement>;
where you can then use simple language to compute
the value of the variable.
Each statement must end with semicolon and new-line (
;\n
). You can only have 1 statement per line.
This makes the init block more similar to Java programming language,
and it was also necessary to make this work for the internal simple parser used by Camel.
|
Here are a couple of examples:
$init{
$cheese := 'Hello ${body}';
$minAge := 18;
$foo := ${uppercase('Hello ${body}')};
$bar := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
You can have Java style code comments in the init block using
// comment here
as follows:
$init{
// minimum age to drive
$minAge := 18;
// say hello to my friend
$foo := ${uppercase('Hello ${body}')};
// either gold or silver
$bar := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
The local variables can then easily be used in the Simple language either using the
${variable.foo}
syntax
or the shorthand syntax
$foo
.
For example as below where we do a basic JSON mapping:
$init{
$greeting := ${uppercase('Hello ${body}')};
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
{
"message": "$greeting",
"status": "$level"
}
The assigned variables from the
init block
are stored as
Variables
on the
Exchange
which
allows to use the variables later in the Camel routes.
For example:
-
Java
-
XML
-
YAML
from("direct:welcome")
.transform().simple(
"""
$init{
$greeting := ${uppercase('Hello ${body}')};
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
{
"message": "$greeting",
"status": "$level"
}
""")
.to("kafka:welcome")
.log("Sending welcome email to customer with status ${variable.level}");
<route>
<from uri="direct:welcome"/>
<transform>
<simple>
$init{
$greeting := ${uppercase('Hello ${body}')};
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
{
"message": "$greeting",
"status": "$level"
}
</simple>
</transform>
<to uri="kafka:welcome"/>
<log message="Sending welcome email to customer with status ${variable.level}"/>
</route>
- from:
uri: direct:welcome
steps:
- transform:
simple:
expression: |-
$init{
$greeting := ${uppercase('Hello ${body}')};
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
}init$
{
"message": "$greeting",
"status": "$level"
}
- to:
uri: kafka:welcome
- log:
message: "Sending welcome email to customer with status ${variable.level}"
Notice how we can refer to the variable (
$level
) in the log statement using the standard
${variable.xxx}
syntax.
Instead of inlining the simple script in the route, you can externalize this to a source file such as
mymapping.txt
and then refer to the file such as
resource:classpath:mymapping.txt
where the file is located in the root classpath (can also be located in sub packages).
|
Init Blocks with custom functions
You can also declare custom functions using
Inside the init block, it is a lso possible to define custom functions in the syntax
$nane ~:= <statement>;
where you can then use simple language to declare
the structure of the function. Then you can later use these custom functions in your simple language expressions.
For example to create a function that can cleanup a
String
value:
$init{
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
}init$
The function will by default use the message body as the input, such that the following:
simple("Incoming message: $cleanUp()");
Would then call the clean function with the message body as the input, which will then be used for trim, normalize and upper-casing.
A custom function can also use a
single
parameter, however then the syntax must use
${name(…)}
style, such as:
simple("Incoming message: ${cleanUp(' Please clean this text for me ')}");
Here the parameter is a fixed
String
but you can also pass in other type of value such as a number, boolean, or even a nested function.
Calling custom functions from custom functions
It is also possible to reuse custom functions from other functions using
${function(name)}
, such shown:
$init{
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
$count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()};
}init$
Here the
$count
is declared as a function which calls the cleanUp function and then counts the words via split and size functions.
Instead of using
${function(name)}
you can also same syntax as the built-in Simple functions, as follows:
$init{
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
$count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()};
}init$
Notice how
${cleanUp()}
despite being a custom function is using the same syntax as all the built-in functions that are being used, for example:
${trim()}
.
EIP Examples
Below we filter based on a header value:
-
Java
-
XML
-
YAML
from("seda:orders")
.filter().simple("${header.foo}")
.to("seda:fooOrders");
<route>
<from uri="seda:orders"/>
<filter>
<simple>${header.foo}</simple>
<to uri="seda:fooOrders"/>
</filter>
</route>
- route:
from:
uri: seda:orders
steps:
- filter:
simple: "${header.foo}"
steps:
- to:
uri: seda:fooOrders
The Simple language can be used for the predicate test above in the
Message Filter pattern, where we test if the
in message has a
foo
header (a header with the key
foo
exists). If
the expression evaluates to
true
, then the message is routed to the
seda:fooOrders
endpoint, otherwise the message is dropped.
You can also use the simple language for simple text concatenations such as:
-
Java
-
XML
-
YAML
from("direct:hello")
.transform().simple("Hello ${header.user} how are you?")
.to("mock:reply");
<route>
<from uri="direct:hello"/>
<transform>
<simple>Hello ${header.user} how are you?</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:hello
steps:
- transform:
simple: "Hello ${header.user} how are you?"
- to:
uri: mock:reply
Notice that we must use
${ }
placeholders in the expression now to
allow Camel to parse it correctly.
And this sample uses the date command to output current date.
-
Java
-
XML
-
YAML
from("direct:hello")
.transform().simple("The today is ${date:now:yyyyMMdd} and it is a great day.")
.to("mock:reply");
<route>
<from uri="direct:hello"/>
<transform>
<simple>The today is ${date:now:yyyyMMdd} and it is a great day.</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:hello
steps:
- transform:
simple: "The today is ${date:now:yyyyMMdd} and it is a great day."
- to:
uri: mock:reply
And in the sample below, we invoke the bean language to invoke a method on a bean to be included in the returned string:
-
Java
-
XML
-
YAML
from("direct:order")
.transform().simple("OrderId: ${bean:orderIdGenerator}")
.to("mock:reply");
<route>
<from uri="direct:order"/>
<transform>
<simple>OrderId: ${bean:orderIdGenerator}</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:order
steps:
- transform:
simple: "OrderId: ${bean:orderIdGenerator}"
- to:
uri: mock:reply
Where
orderIdGenerator
is the id of the bean registered in the
Registry. If using Spring, then it is the Spring bean
id.
If we want to declare which method to invoke on the order id generator
bean we must prepend
.method name
such as below where we invoke the
generateId
method.
-
Java
-
XML
-
YAML
from("direct:order")
.transform().simple("OrderId: ${bean:orderIdGenerator.generateId}")
.to("mock:reply");
<route>
<from uri="direct:order"/>
<transform>
<simple>OrderId: ${bean:orderIdGenerator.generateId}</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:order
steps:
- transform:
simple: "OrderId: ${bean:orderIdGenerator.generateId}"
- to:
uri: mock:reply
We can use the
?method=methodname
option that we are familiar with the
Bean
component itself:
-
Java
-
XML
-
YAML
from("direct:order")
.transform().simple("OrderId: ${bean:orderIdGenerator?method=generateId}")
.to("mock:reply");
<route>
<from uri="direct:order"/>
<transform>
<simple>OrderId: ${bean:orderIdGenerator?method=generateId}</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:order
steps:
- transform:
simple: "OrderId: ${bean:orderIdGenerator?method=generateId}"
- to:
uri: mock:reply
You can also convert the body to a given type, for example, to ensure that it is a String you can do:
<transform>
<simple>Hello ${bodyAs(String)} how are you?</simple>
</transform>
There are a few types which have a shorthand notation, so we can use
String
instead of
java.lang.String
. These are:
byte[], String, Integer, Long
. All other types must use their FQN
name, e.g.
org.w3c.dom.Document
.
It is also possible to look up a value from a header
Map
:
<transform>
<simple>The gold value is ${header.type[gold]}</simple>
</transform>
In the code above we look up the header with name
type
and regard it as
a
java.util.Map
and we then look up with the key
gold
and return the
value. If the header is not convertible to Map, an exception is thrown. If the
header with name
type
does not exist
null
is returned.
You can nest functions, such as shown below:
<setHeader name="myHeader">
<simple>${properties:${header.someKey}}</simple>
</setHeader>
Using Substring Function
You can use the
substring
function to more easily clip the message body.
For example if the message body contains the following 10 letters
ABCDEFGHIJ
then:
<setBody>
<simple>${substring(3)}</simple>
</setBody>
Then the message body after the substring will be
DEFGHIJ
.
If you want to clip from the end instead, then use negative values such as
substring(-3)
.
You can also clip from both ends at the same time such as
substring(1,-1)
that will clip the first and last character in the String.
If the number is higher than the length of the message body, then an empty string is returned, for example
substring(99)
.
Instead of the message body then a simple expression can be nested as input, for example, using a variable, as shown below:
<setBody>
<simple>${substring(1,-1,${variable.foo})}</simple>
</setBody>
Replacing double and single quotes
You can use the
replace
function to more easily replace all single or double quotes in the message body,
using the XML escape syntax. This avoids to fiddle with enclosing a double quote or single quotes with outer quotes,
that can get confusing to be correct as you may need to escape the quotes as well. So instead you can use the XML escape
syntax where double quote is
"
and single quote is
'
(yeah that is the name).
For example, to replace all double quotes with single quotes:
-
Java
-
XML
-
YAML
from("direct:order")
.transform().simple("${replace(" , ')}")
.to("mock:reply");
<route>
<from uri="direct:order"/>
<transform>
<simple>${replace(&quot; , &apos;)}</simple>
</transform>
<to uri="mock:reply"/>
</route>
- route:
from:
uri: direct:order
steps:
- transform:
simple: "${replace(" , ')}"
- to:
uri: mock:reply
And to replace all single quotes with double quotes:
<setBody>
<simple>${replace(' , ")}</simple>
</setBody>
Or to remove all double quotes:
<setBody>
<simple>${replace(" , ∅)}</simple>
</setBody>
Setting the result type
You can now provide a result type to the Simple expression, which means the result of the evaluation will be converted to the desired type. This is most usable to define types such as booleans, integers, etc.
For example, to set a header as a boolean type, you can do:
.setHeader("cool", simple("true", Boolean.class))
And in XML DSL
<setHeader name="cool">
<!-- use resultType to indicate that the type should be a java.lang.Boolean -->
<simple resultType="java.lang.Boolean">true</simple>
</setHeader>
Using new lines or tabs in XML DSLs
It is easier to specify new lines or tabs in XML DSLs as you can escape the value now
<transform>
<simple>The following text\nis on a new line</simple>
</transform>
Leading and trailing whitespace handling
The trim attribute of the expression can be used to control whether the leading and trailing whitespace characters are removed or preserved. The default value is true, which removes the whitespace characters.
<setBody>
<simple trim="false">You get some trailing whitespace characters. </simple>
</setBody>
Loading script from external resource
You can externalize the script and have Camel load it from a resource
such as
"classpath:"
,
"file:"
, or
"http:"
.
This is done using the following syntax:
"resource:scheme:location"
,
e.g., to refer to a file on the classpath you can do:
.setHeader("myHeader").simple("resource:classpath:mysimple.txt")
Pretty XML or JSON
From Camel 4.18 onwards then the Simple language can pretty format the output.
You turn this on via the
pretty
option set to
true
:
-
Java
-
XML
-
YAML
from("direct:json")
.setBody().simple("{ \"name\": \"Jack\", \"age\": 44 }", true)
.to("mock:result");
<route>
<from uri="direct:json"/>
<setBody>
<simple pretty="true">{ "name": "Jack", "age": 44 }</simple>
</setBody>
<to uri="mock:result"/>
</route>
- route:
from:
uri: direct:json
steps:
- setBody:
simple:
expression: "{ \"name\": \"Jack\", \"age\": 44 }"
pretty: true
- to:
uri: mock:result
This works with XML, JSON and text content:
from("direct:xml")
.setBody().simple("<person><name>Jack</name></person>", true)
.to("mock:result");
from("direct:text")
.setBody().simple("Hello ${body}", true)
.to("mock:result");
Adding custom functions to Simple language
You can add custom functions to the Simple language by adding to the
org.apache.camel.spi.SimpleFunctionRegistry
.
A custom function should either be an
Expression
or a
org.apache.camel.spi.SimpleFunction
implementation.
You may want to use
Expression
when you build custom functions from the built-in functions in Camel.
This can be done programmatically such as:
SimpleFunctionRegistry reg = PluginHelper.getSimpleFunctionRegistry(getCamelContext());
reg.addFunction("foo", new ExpressionAdapter() {
@Override
public Object evaluate(Exchange exchange) {
return "I was here " + exchange.getMessage().getBody();
}
});
Here we add a custom function named
foo
which is implemented as a
org.apache.camel.Expression
which conveniently
can be done by using the
org.apache.camel.support.ExpressionAdapter
class as above.
The foo function can now be used in simple such as:
-
Java
-
XML
-
YAML
from("direct:start")
.setBody(simple("Hello ${foo}"))
.to("mock:result");
<route>
<from uri="direct:start"/>
<setBody>
<simple>Hello ${foo}</simple>
</setBody>
<to uri="mock:result"/>
</route>
- route:
from:
uri: direct:start
steps:
- setBody:
simple: "Hello ${foo}"
- to:
uri: mock:result
Notice how the foo function is used just like it was a built-in function using
${foo}
syntax.
Since a custom function is just an
Expression
then it’s possible to build custom functions from
all the Camel languages such as Groovy, JSonPath and even Simple as well.
var bar = context.resolveLanguage("simple").createExpression("${trim()} ~> ${normalizeWhitespace()} ~> ${capitalize()} ~> ${quote()}");
reg.addFunction("bar", bar);
The
bar
function is build using simple functions using the
~>
chain operators to perform a sequence of functions on the payload.
Custom functions will by default use the message body as the payload. But they also support a single parameter as the input, which allows to provide the input as fixed value such as a string literal or to refer to an existing header or variable.
For example the foo function uses variable with name
msg
as the input:
-
Java
-
XML
-
YAML
from("direct:start")
.setVariable("msg", constant("Moon"))
.setBody(simple("Bye ${foo(${variable.msg})}"))
.to("mock:result");
<route>
<from uri="direct:start"/>
<setVariable name="msg">
<constant>Moon</constant>
</setVariable>
<setBody>
<simple>Bye ${foo(${variable.msg})}</simple>
</setBody>
<to uri="mock:result"/>
</route>
- route:
from:
uri: direct:start
steps:
- setVariable:
name: msg
constant: "Moon"
- setBody:
simple: "Bye ${foo(${variable.msg})}"
- to:
uri: mock:result
And instead of nesting the variable inside the foo function, we can also use the
~>
chain operator:
-
Java
-
XML
-
YAML
from("direct:start")
.setVariable("msg", constant("Moon"))
.setBody(simple("Bye ${variable.msg} ~> ${foo}"))
.to("mock:result");
<route>
<from uri="direct:start"/>
<setVariable name="msg">
<constant>Moon</constant>
</setVariable>
<setBody>
<simple>Bye ${variable.msg} ~> ${foo}</simple>
</setBody>
<to uri="mock:result"/>
</route>
- route:
from:
uri: direct:start
steps:
- setVariable:
name: msg
constant: "Moon"
- setBody:
simple: "Bye ${variable.msg} ~> ${foo}"
- to:
uri: mock:result
You can also build custom functions as a Java class as below:
public class FooSimpleFunction implements SimpleFunction {
@Override
public String getName() {
return "foo";
}
@Override
public Object apply(Exchange exchange, Object input) throws Exception {
return "I was here " + input;
}
}
This gives you the full power to implement the function logic in standard Java.
This function can be added programmatically:
SimpleFunctionRegistry reg = PluginHelper.getSimpleFunctionRegistry(getCamelContext());
reg.addFunction(new FooSimpleFunction());
The custom function can then be made discoverable by Camel by dependency injection.
If you use standalone Camel you can add
@BindToRegistry("foo-function")
to the class.
For Spring Boot use
@Component
or
@Service
and Quarkus you can for example use
@ApplicationScoped
.
|
JavaScript Validator
The
camel-catalog
JAR includes a self-contained JavaScript validator for the Simple language.
It can syntax-check expressions and predicates in any browser or Node.js environment — no Java backend required.
The files are located in the JAR under:
org/apache/camel/catalog/simple/camel-simple-validator.js org/apache/camel/catalog/simple/camel-simple-validator.html
The
.js
file is auto-generated from
simple.json
during the Maven build, so its list of functions and operators
always matches the current Camel version.
API
The validator exposes these functions:
-
validate(input, mode)— validate a Simple expression or predicate.modeis'expression'(default) or'predicate'. Returns{ valid: boolean, diagnostics: […] }. -
complete(input, pos)— return completion suggestions at the given cursor position. -
getFunctions()— return the list of known Simple function names. -
getOperators()— return the list of known Simple operators with their kind and description.
Usage in Node.js
const { validate, complete } = require('./camel-simple-validator.js');
const result = validate("${body} == 'hello'", 'predicate');
console.log(result.valid); // true
console.log(result.diagnostics); // []
const bad = validate("${bdy}", 'expression');
console.log(bad.valid); // false
console.log(bad.diagnostics[0].message); // "Unknown function 'bdy', did you mean 'body'?"