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,

Reply via email to