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 42873b157acf CAMEL-26311: camel-core-languages - Simple: a predicate
inside ${ } with equals, !equals, !startsWith or !endsWith, or with a quoted
argument on the left, is read as one (#28714)
42873b157acf is described below
commit 42873b157acfa29919e8fb0e63c13907f9981c97
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 30 21:58:37 2026 +0530
CAMEL-26311: camel-core-languages - Simple: a predicate inside ${ } with
equals, !equals, !startsWith or !endsWith, or with a quoted argument on the
left, is read as one (#28714)
A predicate inside ${ } with equals, !equals, !startsWith or !endsWith, or
with a quoted argument on the left, is now read as one predicate. The syntax
hints list every operator. Upgrade guide entry added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/language/simple/SimpleSyntaxHints.java | 34 ++++++----------
.../simple/SimplePredicateInBracesTest.java | 46 ++++++++++++++++++++++
.../language/simple/SimpleSyntaxHintsTest.java | 11 ++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 +++++
4 files changed, 79 insertions(+), 22 deletions(-)
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
index 900c9a3b02a1..a6be4d341287 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
@@ -28,8 +28,9 @@ import java.util.Set;
*/
public final class SimpleSyntaxHints {
- public static final String OPERATORS = "==, =~, !=, <, <=, >, >=,
contains, !contains, ~~, !~~, regex, !regex, in, !in, "
- + "is, !is, range, !range,
startsWith, endsWith, && and ||";
+ public static final String OPERATORS = "==, =~, !=, !=~, <, <=, >, >=,
equals, !equals, contains, !contains, ~~, !~~, "
+ + "regex, !regex, in, !in, is, !is,
range, !range, startsWith, !startsWith, "
+ + "endsWith, !endsWith, && and ||";
/** The value forms an operator accepts on its right hand side. */
public static final String VALUE_FORMS = "a quoted literal 'x', a number,
true, false, null, or a function ${...}";
@@ -82,7 +83,8 @@ public final class SimpleSyntaxHints {
private static final String[] OPERATOR_WORDS = {
"==", "!=", ">=", "<=", ">", "<", "=~", "!=~", "~~", "!~~",
"contains",
- "!contains", "regex", "!regex", "in", "!in", "is", "!is", "range",
"!range", "startsWith", "endsWith", "&&", "||" };
+ "!contains", "regex", "!regex", "in", "!in", "is", "!is", "range",
"!range", "startsWith", "!startsWith",
+ "endsWith", "!endsWith", "equals", "!equals", "&&", "||" };
private SimpleSyntaxHints() {
}
@@ -187,7 +189,7 @@ public final class SimpleSyntaxHints {
" >= ", " <= ", " > ", " < ", " == ", " != ", " =~ ", " !=~ ",
" contains ", " !contains ", " ~~ ", " !~~ ", " regex ", " !regex
",
" in ", " !in ", " is ", " !is ", " range ", " !range ",
- " startsWith ", " !startsWith ", " endsWith ", " !endsWith " };
+ " startsWith ", " !startsWith ", " endsWith ", " !endsWith ", "
equals ", " !equals " };
/**
* Wraps the function references of a predicate written inside {@code ${
}} so that it can be parsed as one:
@@ -378,7 +380,10 @@ public final class SimpleSyntaxHints {
int best = -1;
String bestOp = null;
for (String op : OPERATOR_WORDS) {
- int i = function.indexOf(" " + op + " ");
+ // an operator inside quotes belongs to a literal, such as the
argument of ${uppercase('a') == 'A'}: the
+ // first one outside quotes, brackets and parentheses is the
operator, so a function with a quoted argument
+ // can be compared
+ int i = indexOutsideQuotes(function, " " + op + " ");
if (i > 0 && (best < 0 || i < best)) {
best = i;
bestOp = op;
@@ -387,24 +392,9 @@ public final class SimpleSyntaxHints {
if (bestOp == null) {
return null;
}
+ // an operator inside an argument list that may hold a predicate (iif,
filter, forEach), or inside a key such as
+ // ${header[order in progress]}, is not at the top level, so
indexOutsideQuotes has skipped it
String head = function.substring(0, best);
- if (head.contains("'") || head.contains("\"")) {
- return null;
- }
- int open = 0;
- for (int i = 0; i < head.length(); i++) {
- char c = head.charAt(i);
- if (c == '(' || c == '[') {
- open++;
- } else if (c == ')' || c == ']') {
- open--;
- }
- }
- if (open > 0) {
- // the operator is inside an argument list that may hold a
predicate (iif, filter, forEach),
- // or inside a key such as ${header[order in progress]}
- return null;
- }
if (head.startsWith("properties:") && head.indexOf(':', 11) > 0) {
// the operator word is in the default value:
${properties:msg:value is not set}
return null;
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
index 970adaa9eb77..fe155c01c7b8 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
@@ -93,6 +93,52 @@ public class SimplePredicateInBracesTest extends
LanguageTestSupport {
assertPredicate("${header[n] == 5}", true);
}
+ @Test
+ void testEveryComparisonOperatorInsideTheBraces() {
+ // equals and !equals (CAMEL-24582), !startsWith and !endsWith
(CAMEL-22868) are operators inside the braces
+ // too; the predicate answered false and the expression null, as the
text was read as one header name
+ exchange.getIn().setHeader("type", "gold");
+ assertPredicate("${header.type equals 'gold'}", true);
+ assertPredicate("${header.type equals 'silver'}", false);
+ assertPredicate("${header.type !equals 'silver'}", true);
+ assertPredicate("${header.type !startsWith 'si'}", true);
+ assertPredicate("${header.type !endsWith 'ld'}", false);
+ assertExpression("${header.type equals 'gold'}", true);
+ // the same answers as outside the braces
+ assertPredicate("${header.type} !startsWith 'si'", true);
+ assertPredicate("${header.type} !endsWith 'ld'", false);
+ }
+
+ @Test
+ void testFunctionWithAQuotedArgumentOnTheLeft() {
+ // the comparison was dropped and the value of the function used: the
predicate was true for any non-empty value
+ exchange.getIn().setBody("Hello World");
+ assertPredicate("${substringAfter('Hello ') == 'World'}", true);
+ assertPredicate("${substringAfter('Hello ') == 'Camel'}", false);
+ assertPredicate("${uppercase('a') == 'B'}", false);
+ assertExpression("${uppercase('a') == 'A'}", true);
+ assertPredicate("${concat('a','b') == 'ab' && body contains 'World'}",
true);
+ // a bracket or a parenthesis inside the quotes is text too
+ assertPredicate("${replace('[', '(') != 'Hello World'}", false);
+ assertPredicate("${replace('(', '[') == 'Hi'}", false);
+ }
+
+ @Test
+ void testAnOperatorInsideQuotesIsPartOfTheArgument() {
+ exchange.getIn().setBody("a == b");
+ assertExpression("${replace(' == ','=')}", "a=b");
+ assertExpression("${properties:msg:a == b}", "a == b");
+ }
+
+ @Test
+ void testEqualsInTheConditionOfATernaryInsideTheBraces() {
+ // the condition of a ternary is wrapped with the same operators:
equals was not one of them, so the condition
+ // was refused as text outside ${ }
+ exchange.getIn().setHeader("type", "gold");
+ assertExpression("${header.type equals 'gold' ? 'yes' : 'no'}", "yes");
+ assertExpression("${header.type !equals 'gold' ? 'yes' : 'no'}", "no");
+ }
+
@Test
public void testTheTernaryStillWins() {
exchange.getIn().setHeader("n", 5);
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
index 412106724618..0c353aa0bbad 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
@@ -95,6 +95,17 @@ public class SimpleSyntaxHintsTest extends
ExchangeTestSupport {
assertThat(predicateError("${body} == 'x' || ")).contains("needs a
predicate on the right hand side");
}
+ @Test
+ public void testUnknownOperatorListsEveryOperator() {
+ // the hint lists every binary operator of the tokenizer, including
the negated and the text-only ones
+ String message = predicateError("${body} = 'x'");
+ String operators = message.substring(message.indexOf("Operators are ")
+ 14, message.indexOf(" at location"));
+ List<String> listed = List.of(operators.split(", | and "));
+ assertThat(listed).containsExactlyInAnyOrder("==", "=~", "!=", "!=~",
"<", "<=", ">", ">=", "equals", "!equals",
+ "contains", "!contains", "~~", "!~~", "regex", "!regex", "in",
"!in", "is", "!is", "range", "!range",
+ "startsWith", "!startsWith", "endsWith", "!endsWith", "&&",
"||");
+ }
+
@Test
public void testQuotes() {
assertThat(predicateError("${body} == 'it''s'")).contains("double
quotes");
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 21a2e9379c0f..ecaa21722e6b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -157,6 +157,16 @@ graphql, kubernetes-custom-resources and typesafe-ai) now
have type converters t
or `InputStream`, so such a body sent to http or a file failed. A `JsonObject`
or `JsonArray` that holds a value that
is not JSON, such as a POJO, converts as before.
+=== camel-core - Simple: more predicates inside ${ } are read as predicates
+
+A predicate written inside the braces (CAMEL-24921, 4.23) is now also read as
one when it uses the operators
+`equals`, `!equals`, `!startsWith` or `!endsWith`, and when its left side is a
function with a quoted argument, such
+as `+${substringAfter('ID-') == '42'}+`. Before, the whole text was read as
one function:
+`+${header.type equals 'gold'}+` answered null (a predicate that never
matches) and `+${uppercase('a') == 'B'}+`
+answered `A` (a predicate that always matches). The condition of a ternary
inside the braces accepts `equals` and
+`!equals` as well: `+${header.type equals 'gold' ? 'a' : 'b'}+` was refused.
The same predicates written outside the
+braces, such as `+${header.type} equals 'gold'+`, are unchanged.
+
=== camel-core - Simple: the elvis operator inside the braces
The elvis operator can now be written inside the braces: `${header.limit ?:
10}` means `${header.limit} ?: 10`.