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 6ed7fef6e6b1 CAMEL-22899: camel-core - Add chain operator to simple
language (#21107)
6ed7fef6e6b1 is described below
commit 6ed7fef6e6b12e40adb27e82d216f74f7a29a54d
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jan 28 18:55:08 2026 +0100
CAMEL-22899: camel-core - Add chain operator to simple language (#21107)
* CAMEL-22899: camel-core - Add chain operator to simple language
---
.../modules/languages/pages/simple-language.adoc | 59 ++++++++
.../camel/language/simple/BaseSimpleParser.java | 64 ++++++++-
.../language/simple/SimpleExpressionParser.java | 43 +++++-
.../language/simple/SimpleInitBlockParser.java | 3 +-
.../language/simple/SimplePredicateParser.java | 37 +++++
.../camel/language/simple/SimpleTokenizer.java | 20 +--
.../camel/language/simple/ast/ChainExpression.java | 155 +++++++++++++++++++++
...herOperatorType.java => ChainOperatorType.java} | 23 +--
.../language/simple/types/OtherOperatorType.java | 14 +-
.../language/simple/types/SimpleTokenType.java | 7 +
.../camel/language/simple/types/TokenType.java | 1 +
.../camel/language/simple/SimpleOperatorTest.java | 57 ++++++++
12 files changed, 459 insertions(+), 24 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/BaseSimpleParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
index 2696696b1820..e821b04ec370 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
@@ -27,6 +27,7 @@ import org.apache.camel.Predicate;
import org.apache.camel.language.simple.ast.Block;
import org.apache.camel.language.simple.ast.BlockEnd;
import org.apache.camel.language.simple.ast.BlockStart;
+import org.apache.camel.language.simple.ast.ChainExpression;
import org.apache.camel.language.simple.ast.OtherExpression;
import org.apache.camel.language.simple.ast.SimpleNode;
import org.apache.camel.language.simple.ast.TernaryExpression;
@@ -217,7 +218,7 @@ public abstract class BaseSimpleParser {
}
/**
- * Prepares other expressions.
+ * Prepares chain expressions.
* <p/>
* This process prepares the other expressions in the AST. This is done by
linking the other operator with both the
* right and left hand side nodes, to have the AST graph updated and
prepared properly.
@@ -225,6 +226,67 @@ public abstract class BaseSimpleParser {
* So when the AST node is later used to create the {@link Predicate}s to
be used by Camel then the AST graph has a
* linked and prepared graph of nodes which represent the input expression.
*/
+ protected void prepareChainExpression() {
+ Deque<SimpleNode> stack = new ArrayDeque<>();
+
+ SimpleNode left = null;
+ for (int i = 0; i < nodes.size(); i++) {
+ if (left == null) {
+ left = i > 0 ? nodes.get(i - 1) : null;
+ }
+ SimpleNode token = nodes.get(i);
+ SimpleNode right = i < nodes.size() - 1 ? nodes.get(i + 1) : null;
+
+ if (token instanceof ChainExpression chain) {
+ // remember the chain operator
+ String operator = chain.getOperator().toString();
+
+ if (left == null) {
+ throw new SimpleParserException(
+ "Chain operator " + operator + " has no left hand
side token", token.getToken().getIndex());
+ }
+ if (right == null) {
+ throw new SimpleParserException(
+ "Chain operator " + operator + " has no right hand
side token", token.getToken().getIndex());
+ }
+
+ if (left instanceof ChainExpression chainLeft) {
+ // append to existing chain on right hand side
+ chainLeft.acceptRightNode(right);
+ } else {
+ if (!chain.acceptLeftNode(left)) {
+ throw new SimpleParserException(
+ "Chain operator " + operator + " does not
support left hand side token " + left.getToken(),
+ token.getToken().getIndex());
+ }
+ if (!chain.acceptRightNode(right)) {
+ throw new SimpleParserException(
+ "Chain operator " + operator + " does not
support right hand side token " + right.getToken(),
+ token.getToken().getIndex());
+ }
+
+ // pop previous as we need to replace it with this other
operator
+ stack.pop();
+ stack.push(token);
+ // this token is now the left for the next loop
+ left = token;
+ }
+
+ // advantage after the right hand side
+ i++;
+ } else {
+ // clear left
+ left = null;
+ stack.push(token);
+ }
+ }
+
+ nodes.clear();
+ nodes.addAll(stack);
+ // must reverse as it was added from a stack that is reverse
+ Collections.reverse(nodes);
+ }
+
protected void prepareOtherExpressions() {
Deque<SimpleNode> stack = new ArrayDeque<>();
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 c46c556c86f0..d9c4ab3a7a9d 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.ChainExpression;
import org.apache.camel.language.simple.ast.InitBlockExpression;
import org.apache.camel.language.simple.ast.LiteralExpression;
import org.apache.camel.language.simple.ast.LiteralNode;
@@ -32,6 +33,7 @@ import
org.apache.camel.language.simple.ast.SimpleFunctionStart;
import org.apache.camel.language.simple.ast.SimpleNode;
import org.apache.camel.language.simple.ast.TernaryExpression;
import org.apache.camel.language.simple.ast.UnaryExpression;
+import org.apache.camel.language.simple.types.ChainOperatorType;
import org.apache.camel.language.simple.types.OtherOperatorType;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
import org.apache.camel.language.simple.types.SimpleParserException;
@@ -141,6 +143,7 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
functionText();
unaryOperator();
ternaryOperator();
+ chainOperator();
otherOperator();
nextToken();
}
@@ -159,6 +162,8 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
prepareUnaryExpressions();
// compact and stack ternary expressions
prepareTernaryExpressions();
+ // compact and stack chain expressions
+ prepareChainExpression();
// compact and stack other expressions
prepareOtherExpressions();
@@ -207,13 +212,13 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
// remove all ignored
tokens.removeIf(t -> t.getType().isIgnore());
- // white space should be removed before and after the other operator
+ // white space should be removed before and after the chain/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() || cur.getType().isInit()) {
+ if (cur.getType().isOther() || cur.getType().isChain() ||
cur.getType().isInit()) {
if (prev.getType().isWhitespace()) {
toRemove.add(prev);
}
@@ -298,6 +303,8 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
// there must be a start ternary already, to let this be an end
ternary
ternary.decrementAndGet();
return new TernaryExpression(token);
+ } else if (token.getType().isChain()) {
+ return new ChainExpression(token);
} else if (token.getType().isOther()) {
return new OtherExpression(token);
} else if (token.getType().isInit()) {
@@ -384,7 +391,7 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
protected void templateText() {
// for template, we accept anything but functions / ternary operator /
other operator
while (!token.getType().isFunctionStart() &&
!token.getType().isFunctionEnd() && !token.getType().isEol()
- && !token.getType().isTernary() && !token.getType().isOther())
{
+ && !token.getType().isTernary() && !token.getType().isOther()
&& !token.getType().isChain()) {
nextToken();
}
}
@@ -439,6 +446,36 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
return false;
}
+ protected boolean chainOperator() {
+ if (accept(TokenType.chainOperator)) {
+ // remember the chain operator
+ ChainOperatorType operatorType =
ChainOperatorType.asOperator(token.getText());
+
+ nextToken();
+ // there should be at least one whitespace after the operator
+ expectAndAcceptMore(TokenType.whiteSpace);
+
+ // then we expect either some quoted text, another function, or a
numeric, boolean or null value
+ if (singleQuotedLiteralWithFunctionsText()
+ || doubleQuotedLiteralWithFunctionsText()
+ || functionText()
+ || numericValue()
+ || booleanValue()
+ || nullValue()) {
+ // then after the right hand side value, there should be a
whitespace if there is more tokens
+ nextToken();
+ if (!token.getType().isEol()) {
+ expectAndAcceptMore(TokenType.whiteSpace);
+ }
+ } else {
+ throw new SimpleParserException(
+ "Chain operator " + operatorType + " does not support
token " + token, token.getIndex());
+ }
+ return true;
+ }
+ return false;
+ }
+
protected boolean ternaryOperator() {
if (accept(TokenType.ternaryOperator)) {
nextToken();
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
index 2f3ca8caa03d..6e34eeb0b181 100644
---
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
@@ -125,7 +125,8 @@ class SimpleInitBlockParser extends SimpleExpressionParser {
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,
+ nextToken(TokenType.functionStart, TokenType.unaryOperator,
TokenType.chainOperator, TokenType.otherOperator,
+ TokenType.initVariable,
TokenType.eol);
}
if (accept(TokenType.initVariable)) {
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 2f6ba0ce7c77..0766704ed241 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
@@ -30,6 +30,7 @@ import org.apache.camel.Expression;
import org.apache.camel.Predicate;
import org.apache.camel.language.simple.ast.BinaryExpression;
import org.apache.camel.language.simple.ast.BooleanExpression;
+import org.apache.camel.language.simple.ast.ChainExpression;
import org.apache.camel.language.simple.ast.DoubleQuoteEnd;
import org.apache.camel.language.simple.ast.DoubleQuoteStart;
import org.apache.camel.language.simple.ast.LiteralExpression;
@@ -46,6 +47,7 @@ import org.apache.camel.language.simple.ast.SingleQuoteStart;
import org.apache.camel.language.simple.ast.TernaryExpression;
import org.apache.camel.language.simple.ast.UnaryExpression;
import org.apache.camel.language.simple.types.BinaryOperatorType;
+import org.apache.camel.language.simple.types.ChainOperatorType;
import org.apache.camel.language.simple.types.LogicalOperatorType;
import org.apache.camel.language.simple.types.OtherOperatorType;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
@@ -150,6 +152,7 @@ public class SimplePredicateParser extends BaseSimpleParser
{
&& !unaryOperator()
&& !binaryOperator()
&& !ternaryOperator()
+ && !chainOperator()
&& !otherOperator()
&& !logicalOperator()
&& !isBooleanValue()
@@ -175,6 +178,8 @@ public class SimplePredicateParser extends BaseSimpleParser
{
prepareBlocks();
// compact and stack unary expressions
prepareUnaryExpressions();
+ // compact and stack chain expressions
+ prepareChainExpression();
// compact and stack binary expressions
prepareBinaryExpressions();
// compact and stack ternary expressions
@@ -374,6 +379,8 @@ public class SimplePredicateParser extends BaseSimpleParser
{
return new TernaryExpression(token);
} else if (token.getType().isOther()) {
return new OtherExpression(token);
+ } else if (token.getType().isChain()) {
+ return new ChainExpression(token);
} else if (token.getType().isLogical()) {
return new LogicalExpression(token);
} else if (token.getType().isNullValue()) {
@@ -824,6 +831,36 @@ public class SimplePredicateParser extends
BaseSimpleParser {
return false;
}
+ protected boolean chainOperator() {
+ if (accept(TokenType.chainOperator)) {
+ // remember the chain operator
+ ChainOperatorType operatorType =
ChainOperatorType.asOperator(token.getText());
+
+ nextToken();
+ // there should be at least one whitespace after the operator
+ expectAndAcceptMore(TokenType.whiteSpace);
+
+ // then we expect either some quoted text, another function, or a
numeric, boolean or null value
+ if (singleQuotedLiteralWithFunctionsText()
+ || doubleQuotedLiteralWithFunctionsText()
+ || functionText()
+ || numericValue()
+ || booleanValue()
+ || nullValue()) {
+ // then after the right hand side value, there should be a
whitespace if there is more tokens
+ nextToken();
+ if (!token.getType().isEol()) {
+ expectAndAcceptMore(TokenType.whiteSpace);
+ }
+ } else {
+ throw new SimpleParserException(
+ "Chain operator " + operatorType + " does not support
token " + token, token.getIndex());
+ }
+ return true;
+ }
+ return false;
+ }
+
protected boolean logicalOperator() {
if (accept(TokenType.logicalOperator)) {
// remember the logical operator
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..0335a87a5464 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];
@@ -88,22 +88,26 @@ public class SimpleTokenizer {
// other operators
KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.otherOperator, "?:");
+ // chain operators
+ KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.chainOperator, "~>");
+ KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.chainOperator, "?~>");
+
// 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/ChainExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
new file mode 100644
index 000000000000..955843346854
--- /dev/null
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
@@ -0,0 +1,155 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+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.ChainOperatorType;
+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 chain operator expression in the AST.
+ */
+public class ChainExpression extends BaseSimpleNode {
+
+ private final ChainOperatorType operator;
+ private SimpleNode left;
+ private final List<SimpleNode> right = new ArrayList<>();
+
+ public ChainExpression(SimpleToken token) {
+ super(token);
+ operator = ChainOperatorType.asOperator(token.getText());
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(" " + token.getText() + " ");
+ for (SimpleNode rn : right) {
+ sj.add(rn.toString());
+ }
+ return left + " " + token.getText() + " " + sj;
+ }
+
+ public boolean acceptLeftNode(SimpleNode lef) {
+ this.left = lef;
+ return true;
+ }
+
+ public boolean acceptRightNode(SimpleNode right) {
+ this.right.add(right);
+ return true;
+ }
+
+ public ChainOperatorType getOperator() {
+ return operator;
+ }
+
+ public SimpleNode getLeft() {
+ return left;
+ }
+
+ public List<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);
+
+ final List<Expression> rightExp = new ArrayList<>();
+ for (SimpleNode rn : right) {
+ if (rn instanceof LiteralExpression le) {
+ String text = le.getText();
+ String changed =
StringHelper.removeLeadingAndEndingQuotes(text);
+ if (!changed.equals(text)) {
+ le.replaceText(changed);
+ }
+ }
+ Expression exp = rn.createExpression(camelContext, expression);
+ rightExp.add(exp);
+ }
+
+ if (operator == ChainOperatorType.CHAIN || operator ==
ChainOperatorType.CHAIN_NULL_SAFE) {
+ boolean nullSafe = operator == ChainOperatorType.CHAIN_NULL_SAFE;
+ return createChainExpression(camelContext, leftExp, rightExp,
nullSafe);
+ }
+
+ throw new SimpleParserException("Unknown chain operator " + operator,
token.getIndex());
+ }
+
+ private Expression createChainExpression(
+ final CamelContext camelContext, final Expression leftExp, final
List<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
+ }
+
+ Object originalBody = exchange.getMessage().getBody();
+ try {
+ exchange.getMessage().setBody(value);
+ for (Expression exp : rightExp) {
+ value = exp.evaluate(exchange, Object.class);
+ if (value == null && nullSafe) {
+ return null; // break out
+ }
+ exchange.getMessage().setBody(value);
+ }
+ if (value == null) {
+ return null;
+ }
+ return camelContext.getTypeConverter().convertTo(type,
exchange, value);
+ } finally {
+ // restore original body to avoid side effects
+ exchange.getMessage().setBody(originalBody);
+ }
+ }
+
+ @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("Chain operator " + operator + " not
supported in csimple", 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/ChainOperatorType.java
similarity index 68%
copy from
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
copy to
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/ChainOperatorType.java
index 7ae09a14339d..65f25f379e5c 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/ChainOperatorType.java
@@ -17,22 +17,27 @@
package org.apache.camel.language.simple.types;
/**
- * Types of other operators supported
+ * Types of chain operators supported
*/
-public enum OtherOperatorType {
+public enum ChainOperatorType {
- ELVIS;
+ CHAIN,
+ CHAIN_NULL_SAFE;
- public static OtherOperatorType asOperator(String text) {
- if ("?:".equals(text)) {
- return ELVIS;
+ public static ChainOperatorType asOperator(String text) {
+ if ("~>".equals(text)) {
+ return CHAIN;
+ } else if ("?~>".equals(text)) {
+ return CHAIN_NULL_SAFE;
}
throw new IllegalArgumentException("Operator not supported: " + text);
}
- public static String getOperatorText(OtherOperatorType operator) {
- if (operator == ELVIS) {
- return "?:";
+ public static String getOperatorText(ChainOperatorType operator) {
+ if (operator == CHAIN) {
+ return "~>";
+ } else if (operator == CHAIN_NULL_SAFE) {
+ return "?~>";
}
return "";
}
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-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 6a773d8816f6..f4f6ff780954 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
@@ -194,6 +194,13 @@ public final class SimpleTokenType {
return type == TokenType.ternaryOperator && ":".equals(value);
}
+ /**
+ * Whether the type is chain operator
+ */
+ public boolean isChain() {
+ return type == TokenType.chainOperator;
+ }
+
@Override
public String toString() {
return 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 0cebb226f734..99bc5dbba43b 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
@@ -40,6 +40,7 @@ public enum TokenType {
initOperator,
initVariable,
ternaryOperator,
+ chainOperator,
eol
}
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..5effdce5234d 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
@@ -18,12 +18,15 @@ package org.apache.camel.language.simple;
import org.apache.camel.Exchange;
import org.apache.camel.LanguageTestSupport;
+import org.apache.camel.Predicate;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
import org.apache.camel.spi.Registry;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class SimpleOperatorTest extends LanguageTestSupport {
@@ -894,6 +897,60 @@ 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");
+ // run 2nd time give same result
+ assertExpression("${substringAfter('Hello')} ~> ${trim()} ~>
${uppercase()}", "WORLD");
+
+ exchange.getIn().setBody(" Hello World ");
+ Predicate predicate = context.resolveLanguage("simple")
+ .createPredicate("${substringAfter('Hello')} ~> ${trim()} ~>
${uppercase()} == 'WORLD'");
+ boolean matches = predicate.matches(exchange);
+ assertTrue(matches);
+ // run 2nd time give same result
+ matches = predicate.matches(exchange);
+ assertTrue(matches);
+
+ exchange.getIn().setBody(" Hello Camel ");
+ predicate = context.resolveLanguage("simple")
+ .createPredicate("${substringAfter('Hello')} ~> ${trim()} ~>
${uppercase()} == 'WORLD'");
+ matches = predicate.matches(exchange);
+ assertFalse(matches);
+ }
+
+ @Test
+ public void testChainNullSafe() {
+ exchange.getIn().setBody(null);
+ assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~>
${uppercase()}", null);
+ // run 2nd time give same result
+ assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~>
${uppercase()}", null);
+
+ Predicate predicate = context.resolveLanguage("simple")
+ .createPredicate("${substringAfter('Hello')} ?~> ${collate(2)}
~> ${uppercase()}");
+ boolean matches = predicate.matches(exchange);
+ assertFalse(matches);
+ // run 2nd time give same result
+ matches = predicate.matches(exchange);
+ assertFalse(matches);
+
+ exchange.getIn().setBody("Hello World,Hello Camel");
+ assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~>
${kindOfType()}", "object");
+ // run 2nd time give same result
+ assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~>
${kindOfType()}", "object");
+
+ predicate = context.resolveLanguage("simple")
+ .createPredicate("${substringAfter('Hello')} ?~> ${collate(2)}
~> ${kindOfType()} == 'object'");
+ matches = predicate.matches(exchange);
+ assertTrue(matches);
+ // run 2nd time give same result
+ matches = predicate.matches(exchange);
+ assertTrue(matches);
+ }
+
@Override
protected String getLanguageName() {
return "simple";