This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch chain in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7c7a2beca8a637e78da6a244fd8f0de2bbb3aef9 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jan 28 11:40:56 2026 +0100 CAMEL-22899: camel-core - Add chain operator to simple language --- .../modules/languages/pages/simple-language.adoc | 59 ++++++++++++++++++++++ .../camel/language/simple/SimpleTokenizer.java | 18 ++++--- .../camel/language/simple/ast/OtherExpression.java | 26 ++++++++++ .../language/simple/types/OtherOperatorType.java | 14 ++++- .../camel/language/simple/SimpleOperatorTest.java | 17 +++++++ 5 files changed, 124 insertions(+), 10 deletions(-) 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 aea31cef6a0d..6f1912d6c81d 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 @@ -772,6 +772,28 @@ The following numeric operators can be used: |`--` | To decrement a number by one. The left-hand side must be a function, otherwise parsed as literal. |==== +These operators are unary and requires to be attached directly next to a function: + +[source,text] +---- +${function}OP +---- + +For example to increment the result of the function: + +[source,java] +---- +simple("${header.myNumber}++ > 10"); +---- + +And the same for decrement: + +[source,java] +---- +simple("${header.myNumber}-- < 10"); +---- + + === Other Operators The following other operators can be used: @@ -781,8 +803,12 @@ The following other operators can be used: |Operator |Description |`? :` | The ternary operator evaluates a condition and returns a value based on the result. If the condition is true, the first value (after `?`) is returned; otherwise, the second value (after `:`) is returned. There must be spaces around both `?` and `:` operators. This is similar to the ternary operator in Java. |`?:` | The elvis operator returns the left-hand side if it has an effective Boolean value of true, otherwise it returns the right-hand side. This is useful for providing fallback values when an expression may evaluate to a value with an effective Boolean value of false (such as `null`, `false`, `0`, empty/blank string). +|`~>` | The chain operator is used in the situations where multiple nested functions need to be applied to a value, while making it easy to read. The value on the left-hand-side is evaluated, and set as the new message body before evaluating the right-hand-side function. This concept is similar to the xref:eips:pipeline-eip.adoc[Pipeline EIP]. +|`?~>` | The null-safe chain operator, where the chain will not continue when a function returned `null`. |==== +==== Ternary Operator + The syntax for the ternary operator is: [source,text] @@ -804,6 +830,8 @@ Ternary operators can also be nested to handle multiple conditions: simple("${header.score >= 90 ? 'A' : ${header.score >= 80 ? 'B' : 'C'}}"); ---- +==== Elvis Operator + For example the following elvis operator will return the username header unless its null or empty, which then the default value of `Guest` is returned. @@ -812,6 +840,37 @@ then the default value of `Guest` is returned. simple("${header.username} ?: 'Guest'"); ---- +==== Chain Operator + +IMPORTANT: The chain operator only supports passing results via the message body. This may change in the future to allow more flexible syntax to +specify which parameter to use as input in the next fuction. + +The syntax for the chain operator is: + +[source,text] +---- +${leftValue} ~> ${rightValue} +---- + +And there can be as many chains: + +[source,text] +---- +${leftValue} ~> ${midValue} ~> ${midValue} -> ${rightValue} +---- + +For example if the message body contains `Hello World` then the follow would return `WORLD`: + +[source,java] +---- +simple("${substringAfter('Hello')} ~> ${trim()} ~> ${uppercase()}"); +---- + +The _null safe_ variant (`?~>`) can be used to avoid `NullPointerException` if it's accepted to not continue the chain when any function returned `null`: + +However, many of the simple functions have NPE protection built-in, so this variant is only needed in special situations. + + === Boolean Operators And the following boolean operators can be used to group expressions: 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 6a55dfafe83c..e295d44127c2 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 @@ -27,7 +27,7 @@ import org.apache.camel.util.ObjectHelper; public class SimpleTokenizer { // keep this number in sync with tokens list - private static final int NUMBER_OF_TOKENS = 52; + private static final int NUMBER_OF_TOKENS = 54; private static final SimpleTokenType[] KNOWN_TOKENS = new SimpleTokenType[NUMBER_OF_TOKENS]; @@ -87,23 +87,25 @@ public class SimpleTokenizer { // other operators KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.otherOperator, "?:"); + KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.otherOperator, "~>"); + KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.otherOperator, "?~>"); // unary operators - KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.unaryOperator, "++"); - KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.unaryOperator, "--"); + KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.unaryOperator, "++"); + KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.unaryOperator, "--"); // logical operators - KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, "&&"); - KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator, "||"); + KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.logicalOperator, "&&"); + KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.logicalOperator, "||"); // ternary operators - KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.ternaryOperator, "?"); - KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.ternaryOperator, ":"); + KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.ternaryOperator, "?"); + KNOWN_TOKENS[52] = new SimpleTokenType(TokenType.ternaryOperator, ":"); //binary operator // it is added as the last item because unary -- has the priority // if unary not found it is highly possible - operator is run into. - KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-"); + KNOWN_TOKENS[53] = new SimpleTokenType(TokenType.minusValue, "-"); } /** diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java index 09902ffd6fe0..b7043021c253 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java @@ -87,6 +87,9 @@ public class OtherExpression extends BaseSimpleNode { if (operator == OtherOperatorType.ELVIS) { return createElvisExpression(camelContext, leftExp, rightExp); + } else if (operator == OtherOperatorType.CHAIN || operator == OtherOperatorType.CHAIN_NULL_SAFE) { + boolean nullSafe = operator == OtherOperatorType.CHAIN_NULL_SAFE; + return createChainExpression(camelContext, leftExp, rightExp, nullSafe); } throw new SimpleParserException("Unknown other operator " + operator, token.getIndex()); @@ -112,6 +115,27 @@ public class OtherExpression extends BaseSimpleNode { }; } + private Expression createChainExpression( + final CamelContext camelContext, final Expression leftExp, final Expression rightExp, boolean nullSafe) { + return new Expression() { + @Override + public <T> T evaluate(Exchange exchange, Class<T> type) { + // left is input to right + Object value = leftExp.evaluate(exchange, Object.class); + if (value == null && nullSafe) { + return null; // break out + } + exchange.getMessage().setBody(value); + return rightExp.evaluate(exchange, type); + } + + @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; @@ -137,6 +161,8 @@ public class OtherExpression extends BaseSimpleNode { if (operator == OtherOperatorType.ELVIS) { return "elvis(exchange, " + leftExp + ", " + rightExp + ")"; + } else if (operator == OtherOperatorType.CHAIN) { + throw new SimpleParserException("Chain operator " + operator + " not supported in csimple", token.getIndex()); } throw new SimpleParserException("Unknown other operator " + operator, token.getIndex()); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java index 7ae09a14339d..8440d3a6a797 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java @@ -21,17 +21,27 @@ package org.apache.camel.language.simple.types; */ public enum OtherOperatorType { + CHAIN, + CHAIN_NULL_SAFE, ELVIS; public static OtherOperatorType asOperator(String text) { - if ("?:".equals(text)) { + if ("~>".equals(text)) { + return CHAIN; + } else if ("?~>".equals(text)) { + return CHAIN_NULL_SAFE; + } else if ("?:".equals(text)) { return ELVIS; } throw new IllegalArgumentException("Operator not supported: " + text); } public static String getOperatorText(OtherOperatorType operator) { - if (operator == ELVIS) { + if (operator == CHAIN) { + return "~>"; + } else if (operator == CHAIN_NULL_SAFE) { + return "?~>"; + } else if (operator == ELVIS) { return "?:"; } return ""; diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java index 2fe58b9b5b8c..d4a054783ea1 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java @@ -894,6 +894,23 @@ public class SimpleOperatorTest extends LanguageTestSupport { ">>> Message received from WebSocket Client : Hello World"); } + @Test + public void testChain() { + exchange.getIn().setBody(null); + assertExpression("${substringAfter('Hello')} ~> ${trim()} ~> ${uppercase()}", null); + exchange.getIn().setBody("Hello World"); + assertExpression("${substringAfter('Hello')} ~> ${trim()} ~> ${uppercase()}", "WORLD"); + } + + @Test + public void testChainNullSafe() { + exchange.getIn().setBody(null); + assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~> ${uppercase()}", null); + + exchange.getIn().setBody("Hello World,Hello Camel"); + assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~> ${kindOfType()}", "object"); + } + @Override protected String getLanguageName() { return "simple";
