This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24920 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 1f4e5d713a359657bebd46ce090df2476843636c Author: Claus Ibsen <[email protected]> AuthorDate: Wed Sep 23 11:21:23 2026 +0200 CAMEL-24920: simple - the ternary takes a compound condition ${a > 0 && b < 10 ? 'x' : 'y'} was refused with "Logical operator && needs a predicate on the right hand side", and parentheses did not help, so a ternary could only ever test one comparison. The condition of a ternary inside ${ } is turned into a predicate by wrapping its functions and parsing the text: wrapFunctionsInCondition found the first comparison operator, wrapped its left side and returned the rest untouched, so everything after the first && kept a bare function name. It now splits the condition on && and || outside quotes and wraps each comparison. A quoted operator is still text (${body contains 'a && b' ? ...}), and the word forms and/or are still refused with the message that says to use && and ||. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../camel/catalog/docs/simple-operators.adoc | 7 ++++ .../modules/languages/pages/simple-operators.adoc | 7 ++++ .../language/simple/ast/SimpleFunctionStart.java | 43 ++++++++++++++++++++++ .../camel/language/simple/SimpleOperatorTest.java | 31 ++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc index e5354e79f7db..7ce0e007b06b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc @@ -128,6 +128,13 @@ The syntax for the ternary operator is: ${leftValue OP rightValue ? trueValue : falseValue} ---- +The condition may be several comparisons joined by `&&` or `||`: + +[source,text] +---- +${header.age > 18 && header.country == 'DK' ? 'yes' : 'no'} +---- + The whole ternary must be inside a single `${ }`. Outside `${ }` a Simple expression is a template, so a `?` and a `:` there are literal text and the operator is not evaluated. For example: diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc index e5354e79f7db..7ce0e007b06b 100644 --- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc +++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc @@ -128,6 +128,13 @@ The syntax for the ternary operator is: ${leftValue OP rightValue ? trueValue : falseValue} ---- +The condition may be several comparisons joined by `&&` or `||`: + +[source,text] +---- +${header.age > 18 && header.country == 'DK' ? 'yes' : 'no'} +---- + The whole ternary must be inside a single `${ }`. Outside `${ }` a Simple expression is a template, so a `?` and a `:` there are literal text and the operator is not evaluated. For example: diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java index 523718ec70a7..208741b3bfaf 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java @@ -333,6 +333,49 @@ public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart { return conditionText; } + // the condition may be several comparisons joined by a logical operator, and each of them needs its + // functions wrapped, not only the first (CAMEL-24920) + StringBuilder answer = new StringBuilder(); + int from = 0; + for (int at = logicalOperator(conditionText, 0); at >= 0; at = logicalOperator(conditionText, from)) { + int end = conditionText.indexOf(' ', at + 1); + if (end < 0) { + break; // the operator has nothing after it: leave it to the predicate parser to say so + } + answer.append(wrapComparison(conditionText.substring(from, at).trim())); + answer.append(' ').append(conditionText, at, end).append(' '); + from = end + 1; + } + answer.append(wrapComparison(conditionText.substring(from).trim())); + return answer.toString(); + } + + /** + * The index of the next logical operator ({@code &&} or {@code ||}) outside quotes, or -1. Simple has no word + * forms: {@code and} and {@code or} are refused by the parser with a message that says so. + */ + private static int logicalOperator(String text, int from) { + boolean single = false; + boolean dubble = false; + for (int i = from; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\'' && !dubble) { + single = !single; + } else if (c == '"' && !single) { + dubble = !dubble; + } else if (!single && !dubble && c == ' ') { + for (String op : new String[] { "&& ", "|| " }) { + if (text.startsWith(op, i + 1)) { + return i + 1; + } + } + } + } + return -1; + } + + /** Wraps the left hand side of one comparison with ${} when it is a function reference. */ + private String wrapComparison(String conditionText) { // Find the operator in the condition String[] operators = { " >= ", " <= ", " > ", " < ", " == ", " != ", " =~ ", " !=~ ", 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 a054c868f158..a20eb732c9a2 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 @@ -942,6 +942,37 @@ public class SimpleOperatorTest extends LanguageTestSupport { assertExpression("${body != true ? 'A' : 'B'}", "A"); } + @Test + public void testTernaryWithCompoundCondition() { + // CAMEL-24920: the condition of a ternary may be more than one comparison + exchange.getIn().setBody(5); + assertExpression("${body > 0 && body < 10 ? 'in' : 'out'}", "in"); + assertExpression("${body > 0 || body > 100 ? 'in' : 'out'}", "in"); + assertExpression("${body > 0 && body < 10 && body != 7 ? 'in' : 'out'}", "in"); + + exchange.getIn().setBody(50); + assertExpression("${body > 0 && body < 10 ? 'in' : 'out'}", "out"); + assertExpression("${body > 0 || body > 100 ? 'in' : 'out'}", "in"); + + exchange.getIn().setBody("Hello"); + assertExpression("${body != null && body contains 'ell' ? 'yes' : 'no'}", "yes"); + assertExpression("${body != null && body contains 'xxx' ? 'yes' : 'no'}", "no"); + } + + @Test + public void testTernaryWithCompoundConditionCornerCases() { + exchange.getIn().setBody(5); + // a quoted value that holds the operator text is not a logical operator + assertExpression("${body > 0 && body < 10 ? 'in && out' : 'no'}", "in && out"); + exchange.getIn().setBody("a && b"); + assertExpression("${body contains 'a && b' ? 'yes' : 'no'}", "yes"); + // simple has no word forms, and says so, as it does outside a ternary + exchange.getIn().setBody(5); + Exception e = assertThrows(Exception.class, + () -> context.resolveLanguage("simple").createExpression("${body > 0 and body < 10 ? 'in' : 'out'}")); + assertTrue(e.getMessage().contains("use && for and"), e.getMessage()); + } + @Test public void testTernaryValueForms() { // a value form the ternary accepts must be used as that value and not looked up as a function,
