You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/12/30 15:15:03 UTC

(camel) 06/25: CAMEL-19749: Add variables as concept to Camel

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 <cl...@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));
     }