This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch var in repository https://gitbox.apache.org/repos/asf/camel.git
commit 77bea9c115b33bc7326b240217af919c2d0265e1 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Dec 28 13:43:07 2023 +0100 CAMEL-19749: Add variables as concept to Camel --- .../modules/languages/pages/simple-language.adoc | 15 ++++- .../language/simple/SimpleExpressionBuilder.java | 17 +++++ .../simple/ast/SimpleFunctionExpression.java | 71 ++++++++++++++++++++ .../apache/camel/language/simple/SimpleTest.java | 75 +++++++++++++++++++++- .../org/apache/camel/support/LanguageHelper.java | 15 +++++ .../camel/support/builder/ExpressionBuilder.java | 2 +- 6 files changed, 192 insertions(+), 3 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 4789404a6cb..9a663faf367 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 @@ -127,6 +127,18 @@ classname |headers |Map |refer to the headers +|variable.foo |Object |refer to the foo variable + +|variable[foo] |Object |refer to the foo variable + +|variable.foo.*OGNL* |Object |refer to the foo variable and invoke its +value using a Camel OGNL expression. + +|variableAs(_key_,_type_) |Type |converts the variable to the given type determined by its +classname + +|variables |Map |refer to the variables + |exchangeProperty.foo |Object |refer to the foo property on the exchange |exchangeProperty[foo] |Object |refer to the foo property on the exchange @@ -165,7 +177,8 @@ exceptions (`Exchange.EXCEPTION_CAUGHT`) if the Exchange has any. |date:_command_ |Date |evaluates to a Date object. Supported commands are: *now* for current timestamp, *exchangeCreated* for the timestamp when the current exchange was created, -*header.xxx* to use the Long/Date object header with the key xxx. +*header.xxx* to use the Long/Date object in the header with the key xxx. +*variable.xxx* to use the Long/Date in the variable with the key xxx. *exchangeProperty.xxx* to use the Long/Date object in the exchange property with the key xxx. *file* for the last modified timestamp of the file (available with a File consumer). Command accepts offsets such as: *now-24h* or *header.xxx+1h* or even *now+1h30m-100*. diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java index 0be12c475a1..ea80baf72a8 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java @@ -76,6 +76,21 @@ public final class SimpleExpressionBuilder { }); } + /** + * Returns the expression for the variable invoking methods defined in a simple OGNL + * notation + * + * @param ognl methods to invoke on the variable in a simple OGNL syntax + */ + public static Expression variablesOgnlExpression(final String ognl) { + return new KeyedOgnlExpressionAdapter( + ognl, "variableOgnl(" + ognl + ")", + (exchange, exp) -> { + String text = exp.evaluate(exchange, String.class); + return exchange.getVariable(text); + }); + } + /** * Returns the message history (including exchange details or not) */ @@ -596,6 +611,8 @@ public final class SimpleExpressionBuilder { date = LanguageHelper.dateFromExchangeCreated(exchange); } else if (command.startsWith("header.")) { date = LanguageHelper.dateFromHeader(exchange, command, (e, o) -> tryConvertingAsDate(e, o, command)); + } else if (command.startsWith("variable.")) { + date = LanguageHelper.dateFromVariable(exchange, command, (e, o) -> tryConvertingAsDate(e, o, command)); } else if (command.startsWith("exchangeProperty.")) { date = LanguageHelper.dateFromExchangeProperty(exchange, command, (e, o) -> tryConvertingAsDate(e, o, command)); } else if ("file".equals(command)) { diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java index ddc8fd13425..fb04db0eefa 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java @@ -88,6 +88,11 @@ public class SimpleFunctionExpression extends LiteralExpression { if (answer != null) { return answer; } + // variables + answer = createSimpleExpressionVariables(function, strict); + if (answer != null) { + return answer; + } // custom languages answer = createSimpleCustomLanguage(function, strict); if (answer != null) { @@ -451,6 +456,63 @@ public class SimpleFunctionExpression extends LiteralExpression { return null; } + private Expression createSimpleExpressionVariables(String function, boolean strict) { + // variableAs + String remainder = ifStartsWithReturnRemainder("variableAs(", function); + if (remainder != null) { + String keyAndType = StringHelper.before(remainder, ")"); + if (keyAndType == null) { + throw new SimpleParserException("Valid syntax: ${variableAs(key, type)} was: " + function, token.getIndex()); + } + + String key = StringHelper.before(keyAndType, ","); + String type = StringHelper.after(keyAndType, ","); + remainder = StringHelper.after(remainder, ")"); + if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || ObjectHelper.isNotEmpty(remainder)) { + throw new SimpleParserException("Valid syntax: ${variableAs(key, type)} was: " + function, token.getIndex()); + } + key = StringHelper.removeQuotes(key); + type = StringHelper.removeQuotes(type); + return ExpressionBuilder.variableExpression(key, type); + } + + // variables function + if ("variables".equals(function)) { + return ExpressionBuilder.variablesExpression(); + } + + // variable function + remainder = parseVariable(function); + if (remainder != null) { + // remove leading character (dot, colon or ?) + if (remainder.startsWith(".") || remainder.startsWith(":") || remainder.startsWith("?")) { + remainder = remainder.substring(1); + } + // remove starting and ending brackets + if (remainder.startsWith("[") && remainder.endsWith("]")) { + remainder = remainder.substring(1, remainder.length() - 1); + } + // remove quotes from key + String key = StringHelper.removeLeadingAndEndingQuotes(remainder); + + // validate syntax + boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key); + if (invalid) { + throw new SimpleParserException("Valid syntax: ${variable.name[key]} was: " + function, token.getIndex()); + } + + if (OgnlHelper.isValidOgnlExpression(key)) { + // ognl based variable + return SimpleExpressionBuilder.variablesOgnlExpression(key); + } else { + // regular variable + return ExpressionBuilder.variableExpression(key); + } + } + + return null; + } + private Expression createSimpleCustomLanguage(String function, boolean strict) { // jq String remainder = ifStartsWithReturnRemainder("jq(", function); @@ -1271,6 +1333,15 @@ public class SimpleFunctionExpression extends LiteralExpression { return remainder; } + private String parseVariable(String function) { + String remainder; + remainder = ifStartsWithReturnRemainder("variables", function); + if (remainder == null) { + remainder = ifStartsWithReturnRemainder("variable", function); + } + return remainder; + } + private String createCodeExchangeProperty(final String function) { // exchangePropertyAsIndex String remainder = ifStartsWithReturnRemainder("exchangePropertyAsIndex(", function); diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java index f6d5e1a8586..d0f672df846 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java @@ -62,7 +62,6 @@ import static org.junit.jupiter.api.Assertions.fail; public class SimpleTest extends LanguageTestSupport { - private static final String JAVA8_INDEX_OUT_OF_BOUNDS_ERROR_MSG = "Index: 2, Size: 2"; private static final String INDEX_OUT_OF_BOUNDS_ERROR_MSG = "Index 2 out of bounds for length 2"; @Override @@ -941,6 +940,80 @@ public class SimpleTest extends LanguageTestSupport { } } + @Test + public void testVariables() throws Exception { + exchange.getVariables().putAll(exchange.getMessage().getHeaders()); + exchange.getMessage().removeHeaders("*"); + + Map<String, Object> variables = exchange.getVariables(); + assertEquals(3, variables.size()); + + assertExpression("${variables}", variables); + } + + @Test + public void testVariableKeyWithSpace() throws Exception { + exchange.getVariables().putAll(exchange.getMessage().getHeaders()); + exchange.getMessage().removeHeaders("*"); + + Map<String, Object> variables = exchange.getVariables(); + variables.put("some key", "Some Value"); + assertEquals(4, variables.size()); + + assertExpression("${variableAs(foo,String)}", "abc"); + assertExpression("${variableAs(some key,String)}", "Some Value"); + assertExpression("${variableAs('some key',String)}", "Some Value"); + + assertExpression("${variable[foo]}", "abc"); + assertExpression("${variable[cheese]}", "gauda"); + assertExpression("${variable[some key]}", "Some Value"); + assertExpression("${variable['some key']}", "Some Value"); + + assertExpression("${variables[foo]}", "abc"); + assertExpression("${variables[cheese]}", "gauda"); + assertExpression("${variables[some key]}", "Some Value"); + assertExpression("${variables['some key']}", "Some Value"); + } + + @Test + public void testVariableAs() throws Exception { + exchange.getVariables().putAll(exchange.getMessage().getHeaders()); + exchange.getMessage().removeHeaders("*"); + + assertExpression("${variableAs(foo,String)}", "abc"); + + assertExpression("${variableAs(bar,int)}", 123); + assertExpression("${variableAs(bar, int)}", 123); + assertExpression("${variableAs('bar', int)}", 123); + assertExpression("${variableAs('bar','int')}", 123); + assertExpression("${variableAs('bar','Integer')}", 123); + assertExpression("${variableAs('bar',\"int\")}", 123); + assertExpression("${variableAs(bar,String)}", "123"); + + assertExpression("${variableAs(unknown,String)}", null); + + try { + assertExpression("${variableAs(unknown String)}", null); + fail("Should have thrown an exception"); + } catch (ExpressionIllegalSyntaxException e) { + assertTrue(e.getMessage().startsWith("Valid syntax: ${variableAs(key, type)} was: variableAs(unknown String)")); + } + + try { + assertExpression("${variableAs(fool,String).test}", null); + fail("Should have thrown an exception"); + } catch (ExpressionIllegalSyntaxException e) { + assertTrue(e.getMessage().startsWith("Valid syntax: ${variableAs(key, type)} was: variableAs(fool,String).test")); + } + + try { + assertExpression("${variableAs(bar,XXX)}", 123); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + assertIsInstanceOf(ClassNotFoundException.class, e.getCause()); + } + } + @Test public void testIllegalSyntax() throws Exception { try { diff --git a/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java index fdbb92ce501..7725136bb95 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java @@ -256,6 +256,21 @@ public final class LanguageHelper { return null; } + public static Date dateFromVariable(Exchange exchange, String command, BiFunction<Exchange, Object, Date> orElseFunction) { + final String key = command.substring(command.lastIndexOf('.') + 1); + final Object obj = exchange.getVariable(key); + if (obj instanceof Date) { + return (Date) obj; + } else if (obj instanceof Long) { + return new Date((Long) obj); + } else { + if (orElseFunction != null) { + return orElseFunction.apply(exchange, obj); + } + } + return null; + } + /** * Extracts the creation date from an exchange * diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java index 2b00ee42e3b..b476a8c0e96 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java @@ -232,7 +232,7 @@ public class ExpressionBuilder { * @param typeName the type to convert to as a FQN class name * @return an expression object which will return the header value */ - public static Expression varibleExpression(final String variableName, final String typeName) { + public static Expression variableExpression(final String variableName, final String typeName) { return variableExpression(simpleExpression(variableName), simpleExpression(typeName)); }