This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 64d58324797e CAMEL-22882: simple language - Add support for init block
(#20958)
64d58324797e is described below
commit 64d58324797e9f43c86710bf10412d00b7af71da
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jan 23 11:46:07 2026 +0100
CAMEL-22882: simple language - Add support for init block (#20958)
* CAMEL-22882: simple language - Add support for init block
---
.../jsonpath/JsonPathSimpleInitBlockTest.java | 118 ++++++++++++++
.../modules/languages/pages/simple-language.adoc | 166 +++++++++++++++++++-
.../language/csimple/CSimpleCodeGenerator.java | 3 +-
.../camel/language/simple/BaseSimpleParser.java | 14 +-
.../language/simple/SimpleExpressionParser.java | 67 +++++++-
.../language/simple/SimpleInitBlockParser.java | 174 +++++++++++++++++++++
.../language/simple/SimpleInitBlockTokenizer.java | 91 +++++++++++
.../language/simple/SimplePredicateParser.java | 31 +++-
.../camel/language/simple/SimpleTokenizer.java | 29 ++--
.../language/simple/ast/InitBlockExpression.java | 123 +++++++++++++++
.../{TokenType.java => InitOperatorType.java} | 41 ++---
.../language/simple/types/SimpleTokenType.java | 21 +++
.../camel/language/simple/types/TokenType.java | 3 +
.../camel/language/simple/SimpleInitBlockTest.java | 113 +++++++++++++
.../camel/support/builder/ExpressionBuilder.java | 25 +++
.../camel/support/builder/PredicateBuilder.java | 47 ++++++
.../camel/dsl/yaml/SimpleInitBlockTest.groovy | 60 +++++++
17 files changed, 1084 insertions(+), 42 deletions(-)
diff --git
a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java
new file mode 100644
index 000000000000..ff85426c7e44
--- /dev/null
+++
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class JsonPathSimpleInitBlockTest extends CamelTestSupport {
+
+ private final String MAPPING = """
+ $init{
+ $id := ${jsonpath($.id)}
+ $type := ${header.type}
+ $price := ${jsonpath($.amount)}
+ $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}
+ }init$
+ {
+ "id": "$id",
+ "type": "$type",
+ "amount": $price,
+ "status": "$level"
+ }
+ """;
+
+ private final String MAPPING2 = """
+ $init{
+ $id := ${jsonpath($.id)}
+ $type := ${header.type}
+ $price := ${jsonpath($.amount)}
+ $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}
+ }init$
+ {
+ "id": "$id",
+ "type": "$type",
+ "amount": ${jsonpath($.amount)},
+ "status": "${lowercase($level)}"
+ }
+ """;
+
+ private final String EXPECTED = """
+ {
+ "id": "123",
+ "type": "silver",
+ "amount": 44,
+ "status": "LOW"
+ }""";
+
+ private final String EXPECTED2 = """
+ {
+ "id": "456",
+ "type": "gold",
+ "amount": 888,
+ "status": "high"
+ }""";
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .transform().simple(MAPPING)
+ .log("${body}")
+ .to("mock:order");
+
+ from("direct:start2")
+ .transform().simple(MAPPING2)
+ .log("${body}")
+ .to("mock:order2");
+ }
+ };
+ }
+
+ @Test
+ public void testMapping() throws Exception {
+ getMockEndpoint("mock:order").expectedBodiesReceived(EXPECTED);
+
+ template.sendBodyAndHeader("direct:start", """
+ {
+ "id": 123,
+ "amount": 44
+ }
+ """, "type", "silver");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testMapping2() throws Exception {
+ getMockEndpoint("mock:order2").expectedBodiesReceived(EXPECTED2);
+
+ template.sendBodyAndHeader("direct:start2", """
+ {
+ "id": 456,
+ "amount": 888
+ }
+ """, "type", "gold");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+}
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index bcb8c13cb006..1f543bbaddb0 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -122,8 +122,6 @@ The Simple language has many built-in functions which
allows access to various p
NOTE: Some functions take 1 or more arguments enclosed in parentheses, and
arguments separated by comma (ie `${replace('custID','customerId')}`.
-TIP:
-
=== Camel Functions
[width="100%",cols="10%,10%,80%",options="header",]
@@ -192,6 +190,7 @@ TIP:
[width="100%",cols="10%,10%,80%",options="header",]
|====
+|Function |Response Type |Description
|`collate(size)` | `List` | The collate function iterates the message body and
groups the data into sub lists of specified size. This can be used with the
Splitter EIP to split a message body and group/batch the split sub message into
a group of N sub lists. This method works similar to the collate method in
Groovy.
|`distinct(val1,val2,...)` | `Set` | Returns a set of all the values with
duplicates removed
|`empty(kind)` | `<T>` | *Deprecated* Use `createEmpty` instead.
@@ -207,6 +206,7 @@ TIP:
[width="100%",cols="10%,10%,80%",options="header",]
|====
+|Function |Response Type |Description
|`iif(predicate,trueExp,falseExp`) | `Object` | The inlined if function
evaluates the predicate expression and returns the value of _trueExp_ if the
predicate is `true`, otherwise the value of `falseExp` is returned. This
function is similar to the ternary operator in Java.
|`isEmpty` | `boolean` | Whether the message body is `null` or empty (list/map
types are tested if they have 0 elements).
|`isEmpty(exp)` | `boolean` | Whether the expression is `null` or empty
(list/map types are tested if they have 0 elements).
@@ -256,6 +256,7 @@ TIP:
[width="100%",cols="10%,10%,80%",options="header",]
|====
+|Function |Response Type |Description
|`env.key` | `String` | Refers to the OS system environment variable with the
given key. For example `env.HOME` to refer to the home directory.
|`hash(exp,algorithm)` | `String` | Returns a hashed value (string in hex
decimal) of the given expression. The algorithm can be `SHA-256` (default) or
`SHA3-256`.
|`hostname` | `String` | Returns the local hostname (may be `null` if not
possible to resolve).
@@ -269,8 +270,9 @@ TIP:
=== String Functions
-0[width="100%",cols="10%,10%,80%",options="header",]
+[width="100%",cols="10%,10%,80%",options="header",]
|====
+|Function |Response Type |Description
|`capitalize()` | `String` | Capitalizes the message body as a String value
(upper case every words)
|`capitalize(exp)` | `String` | Capitalizes the expression as a String value
(upper case every words)
|`concat(exp,exp,separator)` | `String` | Performs a string concat using two
expressions (message body as default) with optional separator (uses comma by
default).
@@ -301,6 +303,7 @@ TIP:
[width="100%",cols="10%,10%,80%",options="header",]
|====
+|Function |Response Type |Description
|`jq(exp)` | `Object` | When working with JSon data, then this allows using
the JQ language, for example, to extract data from the message body (in JSon
format). This requires having camel-jq JAR on the classpath.
|`jq(input,exp)` | `Object` | Same as `jq(exp)` but to use the _input_
expression as the source of the JSon document.
|`jsonpath(exp)` | `Object` | When working with JSon data, then this allows
using the JsonPath language, for example, to extract data from the message body
(in JSon format). This requires having camel-jsonpath JAR on the classpath.
@@ -640,6 +643,163 @@ And of course the `||` is also supported. The sample
would be:
simple("${header.title} contains 'Camel' || ${header.type'} == 'gold'")
-----
+== 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.
+
+IMPORTANT: Init Blocks is not supported with
xref:csimple-language.adoc[CSimple] language.
+
+The _init block_ is declared as follows:
+
+[source,text]
+----
+$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
:= ...` where you can then use simple language to declare
+the value of the variable.
+
+In the example below the `foo` variable is assigned a constant value of `Hello
foo`, while `bar` variable is a string value with the dynamic value
+of the message body.
+
+[source,text]
+----
+$init{
+ $foo := 'Hello foo'
+ $bar := 'Hi from ${body}'
+}init$
+----
+
+You can have Java style code comments in the init block using `// comment
here` as follows:
+
+[source,text]
+----
+$init{
+ // foo is a beginner phrase
+ $foo := 'Hello foo'
+
+ // here we can inline functions to get the name of the user from the message
body
+ $bar := 'Hi from ${body}'
+}init$
+----
+
+Yes you can use the full power of the simple language and all its functions,
such as:
+
+[source,text]
+----
+$init{
+ $foo := ${upper('Hello $body}'}
+ $bar := ${iif(${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:
+
+[source,text]
+----
+$init{
+ $greeting := ${upper('Hello $body}'}
+ $level := ${iif(${header.code} > 999,'Gold','Silver')}
+}init$
+{
+ "message": "$greeting",
+ "status": "$level"
+}
+----
+
+The assigned variables from the _init block_ are stored as
xref:manual::variables.adoc[Variables] on the `Exchange` which
+allows to use the variables later in the Camel routes.
+
+For example:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:welcome")
+ .transform().simple(
+ """
+ $init{
+ $greeting := ${upper('Hello $body}'}
+ $level := ${iif(${header.code} > 999,'Gold','Silver')}
+ }init$
+ {
+ "message": "$greeting",
+ "status": "$level"
+ }
+ """)
+ .to("kafka:welcome")
+ .log("Sending welcome email to customer with status ${variable.level}");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:welcome"/>
+ <transform>
+ <simple>
+ $init{
+ $greeting := ${upper('Hello $body}'}
+ $level := ${iif(${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>
+----
+
+YAML::
++
+[source,yaml]
+----
+- from:
+ uri: direct:welcome
+ steps:
+ - transform:
+ simple:
+ expression: |-
+ $init{
+ $greeting := ${upper('Hello $body}'}
+ $level := ${iif(${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.
+
+TIP: 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).
+
== OGNL Expression Support
The xref:simple-language.adoc[Simple] and xref:simple-language.adoc[Bean]
languages support a _OGNL like_ notation for invoking methods (using
reflection) in a fluent builder like style.
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
index f53be1bed93f..ea976ede2bac 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
@@ -163,7 +163,8 @@ public class CSimpleCodeGenerator implements
CamelContextAware {
sb.append("return ");
}
sb.append(script);
- if (!script.endsWith("}") && !script.endsWith(";")) {
+ String trim = script.trim();
+ if (!trim.endsWith("}") && !trim.endsWith(";")) {
sb.append(";");
}
sb.append("\n");
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
index 1cb610ea7974..2696696b1820 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
@@ -55,18 +55,26 @@ public abstract class BaseSimpleParser {
public static final String CODE_END = "]]code@@\"";
protected final CamelContext camelContext;
- protected final String expression;
+ protected String expression;
+ protected final SimpleTokenizer tokenizer;
+ // regular simple expression
protected final List<SimpleToken> tokens = new ArrayList<>();
protected final List<SimpleNode> nodes = new ArrayList<>();
+ // tokenizer state
protected SimpleToken token;
protected int previousIndex;
protected int index;
protected final boolean allowEscape;
protected BaseSimpleParser(CamelContext camelContext, String expression,
boolean allowEscape) {
+ this(camelContext, expression, allowEscape, new SimpleTokenizer());
+ }
+
+ public BaseSimpleParser(CamelContext camelContext, String expression,
boolean allowEscape, SimpleTokenizer tokenizer) {
this.camelContext = camelContext;
this.expression = expression;
this.allowEscape = allowEscape;
+ this.tokenizer = tokenizer;
}
/**
@@ -74,7 +82,7 @@ public abstract class BaseSimpleParser {
*/
protected void nextToken() {
if (index < expression.length()) {
- SimpleToken next = SimpleTokenizer.nextToken(expression, index,
allowEscape);
+ SimpleToken next = tokenizer.nextToken(expression, index,
allowEscape);
// add token
tokens.add(next);
token = next;
@@ -94,7 +102,7 @@ public abstract class BaseSimpleParser {
*/
protected void nextToken(TokenType... filter) {
if (index < expression.length()) {
- SimpleToken next = SimpleTokenizer.nextToken(expression, index,
allowEscape, filter);
+ SimpleToken next = tokenizer.nextToken(expression, index,
allowEscape, filter);
// add token
tokens.add(next);
token = next;
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
index f8042976310a..2179907af405 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.CamelContext;
import org.apache.camel.Expression;
+import org.apache.camel.language.simple.ast.InitBlockExpression;
import org.apache.camel.language.simple.ast.LiteralExpression;
import org.apache.camel.language.simple.ast.LiteralNode;
import org.apache.camel.language.simple.ast.OtherExpression;
@@ -63,10 +64,45 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
this.skipFileFunctions = skipFileFunctions;
}
+ public SimpleExpressionParser(CamelContext camelContext, String expression,
+ boolean allowEscape, boolean
skipFileFunctions,
+ Map<String, Expression> cacheExpression,
SimpleTokenizer tokenizer) {
+ super(camelContext, expression, allowEscape, tokenizer);
+ this.cacheExpression = cacheExpression;
+ this.skipFileFunctions = skipFileFunctions;
+ }
+
public Expression parseExpression() {
try {
+ Expression init = null;
+ // are there init block then parse this part only, and change the
expression to clip out the init block afterwards
+ if (SimpleInitBlockTokenizer.hasInitBlock(expression)) {
+ SimpleInitBlockParser initParser
+ = new SimpleInitBlockParser(camelContext, expression,
allowEscape, skipFileFunctions, cacheExpression);
+ // the init block should be parsed in predicate mode as that
is needed to fully parse with all the operators and functions
+ init = initParser.parseExpression();
+ if (init != null) {
+ String part = StringHelper.after(expression,
SimpleInitBlockTokenizer.INIT_END);
+ if (part.startsWith("\n")) {
+ // skip newline after ending init block
+ part = part.substring(1);
+ }
+ this.expression = part;
+ // use $$key as local variable in the expression afterwards
+ for (String key : initParser.getInitKeys()) {
+ this.expression = this.expression.replace("$" + key,
"${variable." + key + "}");
+ }
+ }
+ }
+
+ // parse simple expression
parseTokens();
- return doParseExpression();
+ Expression exp = doParseExpression();
+ // include init block in expression
+ if (init != null) {
+ exp = ExpressionBuilder.concatExpression(List.of(init, exp));
+ }
+ return exp;
} catch (SimpleParserException e) {
// catch parser exception and turn that into a syntax exceptions
throw new SimpleIllegalSyntaxException(expression, e.getIndex(),
e.getMessage(), e);
@@ -146,20 +182,38 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
}
}
+ /**
+ * Second step parsing into an expression
+ */
+ protected Expression doParseInitExpression() {
+ // create and return as a Camel expression
+ List<Expression> expressions = createExpressions();
+ if (expressions.isEmpty()) {
+ return null;
+ } else if (expressions.size() == 1) {
+ return expressions.get(0);
+ } else {
+ return ExpressionBuilder.eval(expressions);
+ }
+ }
+
/**
* Removes any ignorable whitespace tokens before and after other
operators.
* <p/>
* During the initial parsing (input -> tokens), then there may be
excessive whitespace tokens, which can safely be
* removed, which makes the succeeding parsing easier.
*/
- private void removeIgnorableWhiteSpaceTokens() {
+ protected void removeIgnorableWhiteSpaceTokens() {
+ // remove all ignored
+ tokens.removeIf(t -> t.getType().isIgnore());
+
// white space should be removed before and after the other operator
List<SimpleToken> toRemove = new ArrayList<>();
for (int i = 1; i < tokens.size() - 1; i++) {
SimpleToken prev = tokens.get(i - 1);
SimpleToken cur = tokens.get(i);
SimpleToken next = tokens.get(i + 1);
- if (cur.getType().isOther()) {
+ if (cur.getType().isOther() || cur.getType().isInit()) {
if (prev.getType().isWhitespace()) {
toRemove.add(prev);
}
@@ -167,6 +221,11 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
toRemove.add(next);
}
}
+ if (cur.getType().isInitVariable()) {
+ if (prev.getType().isWhitespace()) {
+ toRemove.add(prev);
+ }
+ }
}
if (!toRemove.isEmpty()) {
@@ -234,6 +293,8 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
return new TernaryExpression(token);
} else if (token.getType().isOther()) {
return new OtherExpression(token);
+ } else if (token.getType().isInit()) {
+ return new InitBlockExpression(token);
}
// by returning null, we will let the parser determine what to do
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
new file mode 100644
index 000000000000..2f3ca8caa03d
--- /dev/null
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.ast.InitBlockExpression;
+import org.apache.camel.language.simple.ast.LiteralNode;
+import org.apache.camel.language.simple.ast.SimpleNode;
+import org.apache.camel.language.simple.types.TokenType;
+import org.apache.camel.util.StringHelper;
+
+class SimpleInitBlockParser extends SimpleExpressionParser {
+
+ private final Set<String> initKeys = new LinkedHashSet<>();
+
+ public SimpleInitBlockParser(CamelContext camelContext, String expression,
boolean allowEscape, boolean skipFileFunctions,
+ Map<String, Expression> cacheExpression) {
+ super(camelContext,
+ StringHelper.between(expression,
SimpleInitBlockTokenizer.INIT_START, SimpleInitBlockTokenizer.INIT_END),
+ allowEscape, skipFileFunctions, cacheExpression, new
SimpleInitBlockTokenizer());
+ }
+
+ public Set<String> getInitKeys() {
+ return initKeys;
+ }
+
+ protected SimpleInitBlockTokenizer getTokenizer() {
+ return (SimpleInitBlockTokenizer) tokenizer;
+ }
+
+ @Override
+ public Expression parseExpression() {
+ // parse init block
+ parseInitTokens();
+ return doParseInitExpression();
+ }
+
+ @Override
+ public String parseCode() {
+ throw new UnsupportedOperationException("Using init blocks with
csimple is not supported");
+ }
+
+ /**
+ * Second step parsing into code
+ */
+ @Override
+ protected String doParseCode() {
+ StringBuilder sb = new StringBuilder(256);
+ for (SimpleNode node : nodes) {
+ String exp = node.createCode(camelContext, expression);
+ if (exp != null) {
+ parseLiteralNode(sb, node, exp);
+ }
+ }
+
+ String code = sb.toString();
+ code = code.replace(BaseSimpleParser.CODE_START, "");
+ code = code.replace(BaseSimpleParser.CODE_END, "");
+ return code;
+ }
+
+ protected List<SimpleNode> parseInitTokens() {
+ clear();
+ initKeys.clear();
+
+ // parse the expression using the following grammar
+ // init statements are variables assigned to functions/operators
+ nextToken();
+ while (!token.getType().isEol()) {
+ initText();
+ functionText();
+ unaryOperator();
+ otherOperator();
+ nextToken();
+ }
+
+ // now after parsing, we need a bit of work to do, to make it easier
to turn the tokens
+ // into an ast, and then from the ast, to Camel expression(s).
+ // hence why there are a number of tasks going on below to accomplish
this
+
+ // remove any ignore and ignorable white space tokens
+ removeIgnorableWhiteSpaceTokens();
+ // prepare for any local variables to use $$ syntax in simple
expression
+
+ // turn the tokens into the ast model
+ parseAndCreateAstModel();
+ // compact and stack blocks (eg function start/end)
+ prepareBlocks();
+ // compact and stack init blocks
+ prepareInitBlocks();
+ // compact and stack unary operators
+ prepareUnaryExpressions();
+ // compact and stack other expressions
+ prepareOtherExpressions();
+
+ return nodes;
+ }
+
+ // special for init blocks that are only available in the top
+ // $$name := <function>
+ // $$name2 := <function>
+ protected boolean initText() {
+ // turn on init mode so the parser can find the beginning of the init
variable
+ getTokenizer().setAcceptInitTokens(true);
+ while (!token.getType().isInitVariable() && !token.getType().isEol()) {
+ // skip until we find init variable/function (this skips code
comments)
+ nextToken(TokenType.functionStart, TokenType.unaryOperator,
TokenType.otherOperator, TokenType.initVariable,
+ TokenType.eol);
+ }
+ if (accept(TokenType.initVariable)) {
+ while (!token.getType().isWhitespace() &&
!token.getType().isEol()) {
+ nextToken();
+ }
+ expect(TokenType.whiteSpace);
+ nextToken();
+ expect(TokenType.initOperator);
+ nextToken();
+ expectAndAcceptMore(TokenType.whiteSpace);
+ // turn off init mode so the parser does not detect init variables
inside functions or literal text
+ // because they may also use := or $$ symbols
+ getTokenizer().setAcceptInitTokens(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ protected void prepareInitBlocks() {
+ List<SimpleNode> answer = new ArrayList<>();
+ for (int i = 1; i < nodes.size() - 2; i++) {
+ SimpleNode token = nodes.get(i);
+ if (token instanceof InitBlockExpression ie) {
+ SimpleNode prev = nodes.get(i - 1);
+ SimpleNode next = nodes.get(i + 1);
+ ie.acceptLeftNode(prev);
+ ie.acceptRightNode(next);
+ answer.add(ie);
+
+ // remember which init variables we have created
+ if (prev instanceof LiteralNode ln) {
+ String key = StringHelper.after(ln.getText(), "$");
+ if (key != null) {
+ key = key.trim();
+ initKeys.add(key);
+ }
+ }
+ }
+ }
+ nodes.clear();
+ nodes.addAll(answer);
+ }
+
+}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java
new file mode 100644
index 000000000000..65339cd2b562
--- /dev/null
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import java.util.Arrays;
+
+import org.apache.camel.language.simple.types.SimpleToken;
+import org.apache.camel.language.simple.types.SimpleTokenType;
+import org.apache.camel.language.simple.types.TokenType;
+
+/**
+ * Tokenizer for init blocks to create {@link SimpleToken} from the input.
+ */
+public class SimpleInitBlockTokenizer extends SimpleTokenizer {
+
+ // keep this number in sync with tokens list
+ private static final int NUMBER_OF_TOKENS = 2;
+
+ private static final SimpleTokenType[] INIT_TOKENS = new
SimpleTokenType[NUMBER_OF_TOKENS];
+
+ // optimize to be able to quick check for init block
+ public static final String INIT_START = "$init{";
+ // optimize to be able to quick check for init block
+ public static final String INIT_END = "}init$";
+
+ static {
+ // init
+ INIT_TOKENS[0] = new SimpleTokenType(TokenType.initOperator, ":=");
+ INIT_TOKENS[1] = new SimpleTokenType(TokenType.initVariable, "$");
+ }
+
+ private boolean acceptInitTokens = true; // flag to turn on|off
+
+ /**
+ * Does the expression include a simple init block.
+ *
+ * @param expression the expression
+ * @return <tt>true</tt> if init block exists
+ */
+ public static boolean hasInitBlock(String expression) {
+ if (expression != null) {
+ return expression.startsWith(INIT_START) &&
expression.contains(INIT_END);
+ }
+ return false;
+ }
+
+ protected void setAcceptInitTokens(boolean accept) {
+ this.acceptInitTokens = accept;
+ }
+
+ @Override
+ protected SimpleToken customToken(String expression, int index, boolean
allowEscape, TokenType... filters) {
+ if (!acceptInitTokens) {
+ return null;
+ }
+
+ // it could be any of the known tokens
+ String text = expression.substring(index);
+ for (int i = 0; i < NUMBER_OF_TOKENS; i++) {
+ SimpleTokenType token = INIT_TOKENS[i];
+ if (acceptType(token.getType(), filters)
+ && acceptToken(token, text, expression, index)) {
+ return new SimpleToken(token, index);
+ }
+ }
+
+ boolean initVar =
Arrays.asList(filters).contains(TokenType.initVariable);
+ if (initVar) {
+ // if we are filtering to find an init variable then we need to
ignore when not found
+ char ch = text.charAt(0);
+ return new SimpleToken(new SimpleTokenType(TokenType.ignore,
String.valueOf(ch)), index);
+ }
+
+ return null;
+ }
+
+}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
index a7ad6f8e0d08..2f6ba0ce7c77 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
@@ -54,6 +54,7 @@ import org.apache.camel.language.simple.types.SimpleToken;
import org.apache.camel.language.simple.types.TokenType;
import org.apache.camel.support.ExpressionToPredicateAdapter;
import org.apache.camel.support.builder.PredicateBuilder;
+import org.apache.camel.util.StringHelper;
import static org.apache.camel.support.ObjectHelper.isFloatingNumber;
import static org.apache.camel.support.ObjectHelper.isNumber;
@@ -83,8 +84,33 @@ public class SimplePredicateParser extends BaseSimpleParser {
public Predicate parsePredicate() {
try {
+ Expression init = null;
+ // are there init block then parse this part only, and change the
expression to clip out the init block afterwards
+ if (SimpleInitBlockTokenizer.hasInitBlock(expression)) {
+ SimpleInitBlockParser initParser
+ = new SimpleInitBlockParser(camelContext, expression,
allowEscape, skipFileFunctions, cacheExpression);
+ init = initParser.parseExpression();
+ if (init != null) {
+ String part = StringHelper.after(expression,
SimpleInitBlockTokenizer.INIT_END);
+ if (part.startsWith("\n")) {
+ // skip newline after ending init block
+ part = part.substring(1);
+ }
+ this.expression = part;
+ // use $$key as local variable in the expression afterwards
+ for (String key : initParser.getInitKeys()) {
+ this.expression = this.expression.replace("$" + key,
"${variable." + key + "}");
+ }
+ }
+ }
+
parseTokens();
- return doParsePredicate();
+ Predicate pre = doParsePredicate();
+ // include init block in expression
+ if (init != null) {
+ pre = PredicateBuilder.and(PredicateBuilder.alwaysTrue(init),
pre);
+ }
+ return pre;
} catch (SimpleParserException e) {
// catch parser exception and turn that into a syntax exceptions
throw new SimpleIllegalSyntaxException(expression, e.getIndex(),
e.getMessage(), e);
@@ -393,6 +419,9 @@ public class SimplePredicateParser extends BaseSimpleParser
{
* removed, which makes the succeeding parsing easier.
*/
private void removeIgnorableWhiteSpaceTokens() {
+ // remove all ignored
+ tokens.removeIf(t -> t.getType().isIgnore());
+
// white space can be removed if its not part of a quoted text or
within function(s)
boolean quote = false;
int functionCount = 0;
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
index 8beb7b343522..6a55dfafe83c 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
@@ -24,16 +24,16 @@ import org.apache.camel.util.ObjectHelper;
/**
* Tokenizer to create {@link SimpleToken} from the input.
*/
-public final class SimpleTokenizer {
+public class SimpleTokenizer {
// keep this number in sync with tokens list
private static final int NUMBER_OF_TOKENS = 52;
private static final SimpleTokenType[] KNOWN_TOKENS = new
SimpleTokenType[NUMBER_OF_TOKENS];
- // optimise to be able to quick check for start functions
+ // optimize to be able to quick check for start functions
private static final String[] FUNCTION_START = new String[] { "${",
"$simple{" };
- // optimise to be able to quick check for end function
+ // optimize to be able to quick check for end function
private static final String FUNCTION_END = "}";
static {
@@ -106,10 +106,6 @@ public final class SimpleTokenizer {
KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-");
}
- private SimpleTokenizer() {
- // static methods
- }
-
/**
* Does the expression include a simple function.
*
@@ -146,7 +142,7 @@ public final class SimpleTokenizer {
* @param filter defines the accepted token types to be returned
(character is always used as fallback)
* @return the created token, will always return a token
*/
- public static SimpleToken nextToken(String expression, int index, boolean
allowEscape, TokenType... filter) {
+ public SimpleToken nextToken(String expression, int index, boolean
allowEscape, TokenType... filter) {
return doNextToken(expression, index, allowEscape, filter);
}
@@ -158,11 +154,11 @@ public final class SimpleTokenizer {
* @param allowEscape whether to allow escapes
* @return the created token will always return a token
*/
- public static SimpleToken nextToken(String expression, int index, boolean
allowEscape) {
+ public SimpleToken nextToken(String expression, int index, boolean
allowEscape) {
return doNextToken(expression, index, allowEscape);
}
- private static SimpleToken doNextToken(String expression, int index,
boolean allowEscape, TokenType... filters) {
+ private SimpleToken doNextToken(String expression, int index, boolean
allowEscape, TokenType... filters) {
boolean numericAllowed = acceptType(TokenType.numericValue, filters);
if (numericAllowed) {
// is it a numeric value
@@ -192,11 +188,20 @@ public final class SimpleTokenizer {
}
}
+ SimpleToken custom = customToken(expression, index, allowEscape,
filters);
+ if (custom != null) {
+ return custom;
+ }
+
// fallback and create a character token
char ch = expression.charAt(index);
return new SimpleToken(new SimpleTokenType(TokenType.character,
String.valueOf(ch)), index);
}
+ protected SimpleToken customToken(String expression, int index, boolean
allowEscape, TokenType... filters) {
+ return null;
+ }
+
private static int repositionIndex(String expression, int index,
StringBuilder sb) {
boolean digit = true;
while (digit && index < expression.length()) {
@@ -249,7 +254,7 @@ public final class SimpleTokenizer {
return new SimpleToken(new SimpleTokenType(TokenType.character,
sb.toString()), index, special ? 2 : 1);
}
- private static boolean acceptType(TokenType type, TokenType... filters) {
+ protected static boolean acceptType(TokenType type, TokenType... filters) {
if (filters == null || filters.length == 0) {
return true;
}
@@ -261,7 +266,7 @@ public final class SimpleTokenizer {
return false;
}
- private static boolean acceptToken(SimpleTokenType token, String text,
String expression, int index) {
+ protected static boolean acceptToken(SimpleTokenType token, String text,
String expression, int index) {
if (token.isUnary() && text.startsWith(token.getValue())) {
return evalUnary(token, text, expression, index);
}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitBlockExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitBlockExpression.java
new file mode 100644
index 000000000000..07812ccd961a
--- /dev/null
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitBlockExpression.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple.ast;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.BaseSimpleParser;
+import org.apache.camel.language.simple.types.InitOperatorType;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.language.simple.types.SimpleToken;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * Represents other init block expression in the AST.
+ */
+public class InitBlockExpression extends BaseSimpleNode {
+
+ private final InitOperatorType operator;
+ private SimpleNode left;
+ private SimpleNode right;
+
+ public InitBlockExpression(SimpleToken token) {
+ super(token);
+ operator = InitOperatorType.asOperator(token.getText());
+ }
+
+ @Override
+ public String toString() {
+ return left + " " + token.getText() + " " + right;
+ }
+
+ public boolean acceptLeftNode(SimpleNode lef) {
+ this.left = lef;
+ return true;
+ }
+
+ public boolean acceptRightNode(SimpleNode right) {
+ this.right = right;
+ return true;
+ }
+
+ public InitOperatorType getOperator() {
+ return operator;
+ }
+
+ public SimpleNode getLeft() {
+ return left;
+ }
+
+ public SimpleNode getRight() {
+ return right;
+ }
+
+ @Override
+ public Expression createExpression(CamelContext camelContext, String
expression) {
+ ObjectHelper.notNull(left, "left node", this);
+ ObjectHelper.notNull(right, "right node", this);
+
+ // the expression parser does not parse literal text into
single/double quote tokens
+ // so we need to manually remove leading quotes from the literal text
when using the other operators
+ final Expression leftExp = left.createExpression(camelContext,
expression);
+ if (right instanceof LiteralExpression le) {
+ String text = le.getText();
+ String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+ if (!changed.equals(text)) {
+ le.replaceText(changed);
+ }
+ }
+ final Expression rightExp = right.createExpression(camelContext,
expression);
+
+ if (operator == InitOperatorType.ASSIGNMENT) {
+ return createAssignmentExpression(camelContext, leftExp, rightExp);
+ }
+
+ throw new SimpleParserException("Unknown other operator " + operator,
token.getIndex());
+ }
+
+ private Expression createAssignmentExpression(
+ final CamelContext camelContext, final Expression leftExp, final
Expression rightExp) {
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ String name = leftExp.evaluate(exchange, String.class);
+ name = name.trim();
+ name = StringHelper.after(name, "$", name);
+ Object value = rightExp.evaluate(exchange, Object.class);
+ exchange.setVariable(name, value);
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return left + " " + token.getText() + " " + right;
+ }
+ };
+ }
+
+ @Override
+ public String createCode(CamelContext camelContext, String expression)
throws SimpleParserException {
+ return BaseSimpleParser.CODE_START + doCreateCode(camelContext,
expression) + BaseSimpleParser.CODE_END;
+ }
+
+ private String doCreateCode(CamelContext camelContext, String expression)
throws SimpleParserException {
+ throw new SimpleParserException("Using init blocks with csimple is not
supported", token.getIndex());
+ }
+
+}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
similarity index 60%
copy from
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
copy to
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
index ebd3f73e5818..1ebccdd6a799 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
@@ -17,26 +17,29 @@
package org.apache.camel.language.simple.types;
/**
- * Classifications of known token types.
+ * Types of init operators supported
*/
-public enum TokenType {
+public enum InitOperatorType {
- whiteSpace,
- character,
- booleanValue,
- numericValue,
- nullValue,
- singleQuote,
- doubleQuote,
- minusValue,
- escape,
- functionStart,
- functionEnd,
- binaryOperator,
- otherOperator,
- unaryOperator,
- logicalOperator,
- ternaryOperator,
- eol
+ ASSIGNMENT;
+
+ public static InitOperatorType asOperator(String text) {
+ if (":=".equals(text)) {
+ return ASSIGNMENT;
+ }
+ throw new IllegalArgumentException("Operator not supported: " + text);
+ }
+
+ public static String getOperatorText(InitOperatorType operator) {
+ if (operator == ASSIGNMENT) {
+ return ":=";
+ }
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return getOperatorText(this);
+ }
}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
index eb78ea50c991..ba41ee1a7508 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
@@ -47,6 +47,13 @@ public final class SimpleTokenType {
return value;
}
+ /**
+ * Whether the type is ignore token
+ */
+ public boolean isIgnore() {
+ return type == TokenType.ignore;
+ }
+
/**
* Whether the type is whitespace
*/
@@ -124,6 +131,20 @@ public final class SimpleTokenType {
return type == TokenType.logicalOperator;
}
+ /**
+ * Whether the type is init operator
+ */
+ public boolean isInit() {
+ return type == TokenType.initOperator;
+ }
+
+ /**
+ * Whether the type is init variable
+ */
+ public boolean isInitVariable() {
+ return type == TokenType.initVariable;
+ }
+
/**
* Whether the type is a null value
*/
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
index ebd3f73e5818..0cebb226f734 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
@@ -21,6 +21,7 @@ package org.apache.camel.language.simple.types;
*/
public enum TokenType {
+ ignore,
whiteSpace,
character,
booleanValue,
@@ -36,6 +37,8 @@ public enum TokenType {
otherOperator,
unaryOperator,
logicalOperator,
+ initOperator,
+ initVariable,
ternaryOperator,
eol
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
new file mode 100644
index 000000000000..515d88dc53e0
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import org.apache.camel.LanguageTestSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class SimpleInitBlockTest extends LanguageTestSupport {
+
+ private static final String INIT = """
+ $init{
+ // this is a java like comment
+ $sum := ${sum(${header.lines},100)}
+
+ $sku := ${iif(${body} contains 'Camel',123,999)}
+ }init$
+ orderId=$sku,total=$sum
+ """;
+
+ private static final String INIT2 = """
+ $init{
+ // this is a java like comment
+ $sum := ${sum(${header.lines},100)}
+
+ $sku := ${iif(${body} contains 'Camel',123,999)}
+ }init$
+ $sum > 200 && $sku != 999
+ """;
+
+ private static final String INIT3 = """
+ $init{
+ // this is a java like comment
+ $sum := ${sum(${header.lines},100)}
+
+ $sku := ${iif(${body} contains 'Camel',123,999)}
+ }init$
+ """;
+
+ private static final String INIT4 = """
+ $init{
+ // this is a java like comment
+ $sum := ${sum(${header.lines},100)}
+
+ $sku := ${iif(${body} contains 'Hi := Me $sku',123,999)}
+ }init$
+ orderId=$sku,total=$sum
+ """;
+
+ @Test
+ public void testInitBlockExpression() throws Exception {
+ exchange.getMessage().setBody("Hello Camel");
+ exchange.getMessage().setHeader("lines", "75,33");
+
+ assertExpression(exchange, INIT, "orderId=123,total=208\n");
+ }
+
+ @Test
+ public void testInitBlockOnlyExpression() throws Exception {
+ exchange.getMessage().setBody("Hello Camel");
+ exchange.getMessage().setHeader("lines", "75,33");
+
+ assertExpression(exchange, INIT3, "");
+ Assertions.assertEquals("123", exchange.getVariable("sku"));
+ Assertions.assertEquals(208L, exchange.getVariable("sum"));
+ }
+
+ @Test
+ public void testInitBlockPredicate() throws Exception {
+ exchange.getMessage().setBody("Hello Camel");
+ exchange.getMessage().setHeader("lines", "75,33");
+ assertPredicate(exchange, INIT2, true);
+
+ exchange.getMessage().setBody("Hello Camel");
+ exchange.getMessage().setHeader("lines", "3,5");
+ assertPredicate(exchange, INIT2, false);
+
+ exchange.getMessage().setBody("Hello World");
+ exchange.getMessage().setHeader("lines", "75,99");
+ assertPredicate(exchange, INIT2, false);
+
+ exchange.getMessage().setBody("Hello World");
+ exchange.getMessage().setHeader("lines", "3,5");
+ assertPredicate(exchange, INIT2, false);
+ }
+
+ @Test
+ public void testInitBlockExpressionWithAssignmentInFunction() throws
Exception {
+ exchange.getMessage().setBody("Hello Hi := Me $sku");
+ exchange.getMessage().setHeader("lines", "75,33");
+
+ assertExpression(exchange, INIT4, "orderId=123,total=208\n");
+ }
+
+ @Override
+ protected String getLanguageName() {
+ return "simple";
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index da58f6ca8897..bd5fd489eb37 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -2305,6 +2305,31 @@ public class ExpressionBuilder {
};
}
+ public static Expression eval(List<Expression> expressions) {
+ return new ExpressionAdapter() {
+ @Override
+ public void init(CamelContext context) {
+ super.init(context);
+ for (Expression exp : expressions) {
+ exp.init(context);
+ }
+ }
+
+ @Override
+ public Object evaluate(Exchange exchange) {
+ for (Expression exp : expressions) {
+ exp.evaluate(exchange, Object.class);
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "eval";
+ }
+ };
+ }
+
/**
* Returns an Expression for the inbound message id
*/
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
index d87ee46bdc10..f2a8cf3104f0 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
@@ -37,6 +37,53 @@ import static org.apache.camel.util.ObjectHelper.notNull;
* A helper class for working with predicates
*/
public class PredicateBuilder {
+
+ /**
+ * Executes the expression and always return true
+ */
+ public static Predicate alwaysTrue(final Expression expression) {
+ return new Predicate() {
+ @Override
+ public void init(CamelContext context) {
+ expression.init(context);
+ }
+
+ @Override
+ public boolean matches(Exchange exchange) {
+ expression.evaluate(exchange, Object.class);
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return expression.toString();
+ }
+ };
+ }
+
+ /**
+ * Executes the predicate and always return true
+ */
+ public static Predicate alwaysTrue(final Predicate predicate) {
+ return new Predicate() {
+ @Override
+ public void init(CamelContext context) {
+ predicate.init(context);
+ }
+
+ @Override
+ public boolean matches(Exchange exchange) {
+ predicate.matches(exchange);
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return predicate.toString();
+ }
+ };
+ }
+
/**
* Converts the given expression into an {@link Predicate}
*/
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy
new file mode 100644
index 000000000000..cbc63a01d037
--- /dev/null
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.yaml
+
+import org.apache.camel.component.mock.MockEndpoint
+import org.apache.camel.dsl.yaml.support.YamlTestSupport
+
+class SimpleInitBlockTest extends YamlTestSupport {
+
+ def "initBlock"() {
+ setup:
+ loadRoutes '''
+ - route:
+ from:
+ uri: direct:map
+ steps:
+ - setBody:
+ simple:
+ expression: |-
+ $init{
+ // this is a java like comment
+ $sum := ${sum(${header.lines},100)}
+
+ $sku := ${iif(${body} contains
'Camel',123,999)}
+ }init$
+ orderId=$sku,total=$sum
+ - to:
+ uri: mock:result
+ '''
+ withMock('mock:result') {
+ expectedMessageCount(1)
+ message(0).body().contains("orderId=123,total=208")
+ }
+
+ when:
+ context.start()
+
+ withTemplate {
+ to('direct:map').withHeader("lines", "75,33").withBody('Hello
Camel').send()
+ }
+
+ then:
+ MockEndpoint.assertIsSatisfied(context)
+ }
+
+}