diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java index 21fc2018c5797..8778415806657 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java @@ -1310,6 +1310,10 @@ public static Object setVariable(Exchange exchange, String name, Class type, return null; } + public static boolean matchesValue(Exchange exchange, Object value) { + return ObjectHelper.evaluateValuePredicate(value); + } + public static boolean isNot(Exchange exchange, Object value) { if (value == null) { return true; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java index 535dce4fbfa88..bb7f69a016a40 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java @@ -48,7 +48,7 @@ public String toString() { } public boolean acceptLeftNode(SimpleNode lef) { - if (!(lef instanceof BinaryExpression) && !(lef instanceof LogicalExpression)) { + if (!isValidPredicateOperand(lef)) { return false; } this.left = lef; @@ -56,13 +56,24 @@ public boolean acceptLeftNode(SimpleNode lef) { } public boolean acceptRightNode(SimpleNode right) { - if (!(right instanceof BinaryExpression) && !(right instanceof LogicalExpression)) { + if (!isValidPredicateOperand(right)) { return false; } this.right = right; return true; } + /** + * Predicate operands for logical AND/OR include binary/logical expressions as well as boolean-zen shorthand + * (standalone functions such as {@code ${header.active}} or {@code ${exchangeProperty.flag}}) that are evaluated + * via {@link org.apache.camel.support.ExpressionToPredicateAdapter}. + */ + private static boolean isValidPredicateOperand(SimpleNode node) { + return node instanceof BinaryExpression + || node instanceof LogicalExpression + || node instanceof SimpleFunctionStart; + } + public LogicalOperatorType getOperator() { return operator; } @@ -137,8 +148,8 @@ private String doCreateCode(CamelContext camelContext, String expression) throws ObjectHelper.notNull(left, "left node", this); ObjectHelper.notNull(right, "right node", this); - final String leftExp = left.createCode(camelContext, expression); - final String rightExp = right.createCode(camelContext, expression); + final String leftExp = predicateOperandCode(left, camelContext, expression); + final String rightExp = predicateOperandCode(right, camelContext, expression); if (operator == LogicalOperatorType.AND) { return leftExp + " && " + rightExp; @@ -148,4 +159,15 @@ private String doCreateCode(CamelContext camelContext, String expression) throws throw new SimpleParserException("Unknown logical operator " + operator, token.getIndex()); } + + private static String predicateOperandCode(SimpleNode node, CamelContext camelContext, String expression) + throws SimpleParserException { + String code = node.createCode(camelContext, expression); + code = code.replace(BaseSimpleParser.CODE_START, ""); + code = code.replace(BaseSimpleParser.CODE_END, ""); + if (node instanceof SimpleFunctionStart) { + return "matchesValue(exchange, " + code + ")"; + } + return code; + } } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimplePredicateParserTest.java b/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimplePredicateParserTest.java index 4e9d3e1ac2041..56e4f8f1fa20b 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimplePredicateParserTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimplePredicateParserTest.java @@ -103,4 +103,13 @@ public void testParseExchangeProperty() { "isNotEqualTo(exchange, exchangePropertyAs(exchange, \"foo\", com.foo.User.class).getName(), \"bar\")", code); } + @Test + public void testParseBooleanZenLogicalOr() { + CSimplePredicateParser parser = new CSimplePredicateParser(); + String code = parser.parsePredicate("${header.token} == null || ${exchangeProperty.forceNewSessionToken}"); + Assertions.assertEquals( + "isEqualTo(exchange, header(message, \"token\"), null) || matchesValue(exchange, exchangeProperty(exchange, \"forceNewSessionToken\"))", + code); + } + } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserLogicalTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserLogicalTest.java index bd62f2cb10899..f7e630c301cc4 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserLogicalTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserLogicalTest.java @@ -21,18 +21,17 @@ import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * Regression tests for prepareLogicalExpressions in SimplePredicateParser. Covers the bug where the right-hand token * was reported as the left-hand token in the "does not support right hand side token" error message. */ -public class SimplePredicateParserLogicalTest extends ExchangeTestSupport { +class SimplePredicateParserLogicalTest extends ExchangeTestSupport { @Test - public void testAndWithFunctionRightHandSide() { + void testAndWithFunctionRightHandSide() { exchange.getIn().setBody("hello"); exchange.getIn().setHeader("active", true); @@ -41,11 +40,78 @@ public void testAndWithFunctionRightHandSide() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testAndWithLiteralRightHandSide() { + void testOrWithBooleanZenRightHandSide() { + // CAMEL-24376: boolean-zen shorthand must work as the right operand of || + exchange.getIn().setHeader("token", null); + exchange.setProperty("forceNewSessionToken", true); + + SimplePredicateParser parser = new SimplePredicateParser( + context, "${header.token} == null || ${exchangeProperty.forceNewSessionToken}", true, null); + Predicate predicate = parser.parsePredicate(); + predicate.init(context); + + assertThat(predicate.matches(exchange)).isTrue(); + } + + @Test + void testOrWithBooleanZenLeftHandSide() { + exchange.getIn().setHeader("active", true); + exchange.getIn().setBody("other"); + + SimplePredicateParser parser = new SimplePredicateParser( + context, "${header.active} || ${body} == 'hello'", true, null); + Predicate predicate = parser.parsePredicate(); + predicate.init(context); + + assertThat(predicate.matches(exchange)).isTrue(); + } + + @Test + void testAndWithBooleanZenOperands() { + exchange.getIn().setHeader("enabled", true); + exchange.setProperty("ready", true); + + SimplePredicateParser parser = new SimplePredicateParser( + context, "${header.enabled} && ${exchangeProperty.ready}", true, null); + Predicate predicate = parser.parsePredicate(); + predicate.init(context); + + assertThat(predicate.matches(exchange)).isTrue(); + + exchange.setProperty("ready", false); + assertThat(predicate.matches(exchange)).isFalse(); + } + + @Test + void testOrWithBooleanZenFalseWhenBothOperandsFalse() { + exchange.getIn().setHeader("token", "abc"); + exchange.setProperty("forceNewSessionToken", false); + + SimplePredicateParser parser = new SimplePredicateParser( + context, "${header.token} == null || ${exchangeProperty.forceNewSessionToken}", true, null); + Predicate predicate = parser.parsePredicate(); + predicate.init(context); + + assertThat(predicate.matches(exchange)).isFalse(); + } + + @Test + void testStandaloneBooleanZenStillWorks() { + exchange.getIn().setHeader("foo", "yes"); + + SimplePredicateParser parser = new SimplePredicateParser(context, "${header.foo}", true, null); + Predicate predicate = parser.parsePredicate(); + predicate.init(context); + + assertThat(predicate.matches(exchange)).isTrue(); + } + + @Test + void testAndWithLiteralRightHandSide() { exchange.getIn().setBody("foo"); SimplePredicateParser parser = new SimplePredicateParser( @@ -53,11 +119,11 @@ public void testAndWithLiteralRightHandSide() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testOrWithFunctionRightHandSide() { + void testOrWithFunctionRightHandSide() { exchange.getIn().setBody("hello"); exchange.getIn().setHeader("score", 5); @@ -66,11 +132,11 @@ public void testOrWithFunctionRightHandSide() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testOrAllFalse() { + void testOrAllFalse() { exchange.getIn().setBody("hello"); exchange.getIn().setHeader("score", 1); @@ -79,11 +145,11 @@ public void testOrAllFalse() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertFalse(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isFalse(); } @Test - public void testAndWithNumericRightHandSide() { + void testAndWithNumericRightHandSide() { exchange.getIn().setBody(42); SimplePredicateParser parser = new SimplePredicateParser( @@ -91,11 +157,11 @@ public void testAndWithNumericRightHandSide() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testAndWithNullRightHandSide() { + void testAndWithNullRightHandSide() { exchange.getIn().setBody("present"); exchange.getIn().setHeader("tag", "x"); @@ -104,11 +170,11 @@ public void testAndWithNullRightHandSide() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testChainedAndOrLogicalOperators() { + void testChainedAndOrLogicalOperators() { exchange.getIn().setBody("alpha"); exchange.getIn().setHeader("flag", true); @@ -117,20 +183,18 @@ public void testChainedAndOrLogicalOperators() { Predicate predicate = parser.parsePredicate(); predicate.init(context); - assertTrue(predicate.matches(exchange)); + assertThat(predicate.matches(exchange)).isTrue(); } @Test - public void testInvalidRightHandSideReportsRightToken() { + void testInvalidRightHandSideReportsRightToken() { // "&&" followed by a bare numeric (not a binary expression) is invalid syntax. // The error message must say "right hand side token 42" (the actual offending token), // not "right hand side token ==" (which would indicate the left-hand node was reported). SimplePredicateParser parser = new SimplePredicateParser( context, "${body} == 'foo' && 42", true, null); SimpleIllegalSyntaxException ex = assertThrows(SimpleIllegalSyntaxException.class, parser::parsePredicate); - assertTrue(ex.getMessage().contains("right hand side token 42"), - "Error message should say 'right hand side token 42', but was: " + ex.getMessage()); - assertFalse(ex.getMessage().contains("right hand side token =="), - "Error message must not say 'right hand side token ==', but was: " + ex.getMessage()); + assertThat(ex.getMessage()).contains("right hand side token 42"); + assertThat(ex.getMessage()).doesNotContain("right hand side token =="); } }