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)
+    }
+
+}

Reply via email to