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 3ae2c7e4f24f camel-core - Simple language chain operator supports
calling functions from functions (#21167)
3ae2c7e4f24f is described below
commit 3ae2c7e4f24f37442a26643f4f1fc5919eee67b8
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Feb 1 17:34:39 2026 +0100
camel-core - Simple language chain operator supports calling functions from
functions (#21167)
---
.../org/apache/camel/catalog/languages/simple.json | 13 ++-
.../language/csimple/joor/OriginalSimpleTest.java | 21 ++++
.../apache/camel/spi/SimpleFunctionRegistry.java | 7 ++
.../impl/engine/DefaultSimpleFunctionRegistry.java | 6 +
.../org/apache/camel/language/simple/simple.json | 13 ++-
.../modules/languages/pages/simple-language.adoc | 31 ++++-
.../camel/language/csimple/CSimpleHelper.java | 13 +++
.../camel/language/simple/SimpleConstants.java | 4 +
.../language/simple/SimpleExpressionBuilder.java | 43 ++++++-
.../camel/language/simple/ast/ChainExpression.java | 92 +++++++++------
.../simple/ast/SimpleFunctionExpression.java | 53 +++++++++
.../language/simple/SimpleInitBlockChainTest.java | 71 -----------
.../simple/SimpleInitBlockFunctionTest.java | 130 +++++++++++++++++++++
.../apache/camel/language/simple/SimpleTest.java | 21 ++++
14 files changed, 395 insertions(+), 123 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
index aef6018b1262..4c7afb123c23 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
@@ -128,11 +128,12 @@
"trim(exp)": { "index": 101, "kind": "function", "displayName": "Trim",
"group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The trim function trims
the message body (or expression) by removing all leading and trailing white
spaces.", "ognl": false, "suffix": "}" },
"type:name.field": { "index": 102, "kind": "function", "displayName":
"Java Field Value", "group": "function", "label": "function", "required":
false, "javaType": "Object", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "To
refer to a type or field by its classname. To refer to a field, you can append
.FIELD_NAME. For example, you can refer to the constant field from Exchange as:
`org.apache.camel.Exchange.FILE_NAME`", [...]
"kindOfType(exp)": { "index": 103, "kind": "function", "displayName":
"Kind of Type", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "What kind of type is
the value (null,number,string,boolean,array,object)", "ognl": false, "suffix":
"}" },
- "uppercase(exp)": { "index": 104, "kind": "function", "displayName":
"Uppercase", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Uppercases the message
body (or expression)", "ognl": false, "suffix": "}" },
- "uuid(type)": { "index": 105, "kind": "function", "displayName": "Generate
UUID", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Returns a UUID using the
Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and
`simple` as the type. If no type is given, the default is used. It is also
possible to use a custom `Uuid [...]
- "variable.name": { "index": 106, "kind": "function", "displayName":
"Variable", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The variable with the
given name", "ognl": true, "suffix": "}" },
- "variableAs(key,type)": { "index": 107, "kind": "function", "displayName":
"Variable As", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Converts the variable
to the given type (classname).", "ognl": false, "suffix": "}" },
- "variables": { "index": 108, "kind": "function", "displayName":
"Variables", "group": "function", "label": "function", "required": false,
"javaType": "java.util.Map", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Returns all the variables from the current Exchange in a Map", "ognl": false,
"suffix": "}" },
- "xpath(input,exp)": { "index": 109, "kind": "function", "displayName":
"XPath", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "When working with XML
data, then this allows using the XPath language, for example, to extract data
from the message body (in XML format). This requires having camel-xpath JAR on
the classpath. For input (optiona [...]
+ "unquote(exp)": { "index": 104, "kind": "function", "displayName":
"Unquote", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Returns the message
body (or expression) with any leading\/ending quotes removed", "ognl": false,
"suffix": "}" },
+ "uppercase(exp)": { "index": 105, "kind": "function", "displayName":
"Uppercase", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Uppercases the message
body (or expression)", "ognl": false, "suffix": "}" },
+ "uuid(type)": { "index": 106, "kind": "function", "displayName": "Generate
UUID", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Returns a UUID using the
Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and
`simple` as the type. If no type is given, the default is used. It is also
possible to use a custom `Uuid [...]
+ "variable.name": { "index": 107, "kind": "function", "displayName":
"Variable", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The variable with the
given name", "ognl": true, "suffix": "}" },
+ "variableAs(key,type)": { "index": 108, "kind": "function", "displayName":
"Variable As", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Converts the variable
to the given type (classname).", "ognl": false, "suffix": "}" },
+ "variables": { "index": 109, "kind": "function", "displayName":
"Variables", "group": "function", "label": "function", "required": false,
"javaType": "java.util.Map", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Returns all the variables from the current Exchange in a Map", "ognl": false,
"suffix": "}" },
+ "xpath(input,exp)": { "index": 110, "kind": "function", "displayName":
"XPath", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "When working with XML
data, then this allows using the XPath language, for example, to extract data
from the message body (in XML format). This requires having camel-xpath JAR on
the classpath. For input (optiona [...]
}
}
diff --git
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
index 6e7d6a2ebd12..180913cc4b57 100644
---
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
+++
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
@@ -2682,6 +2682,27 @@ public class OriginalSimpleTest extends
LanguageTestSupport {
assertEquals("\"{A=1, B=2}\"", s);
}
+ @Test
+ public void testUnquote() {
+ exchange.getMessage().setBody("\"Hello World\"");
+
+ Expression expression =
context.resolveLanguage("csimple").createExpression("${unquote()}");
+ String s = expression.evaluate(exchange, String.class);
+ assertEquals("Hello World", s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${unquote(${body})}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hello World", s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${unquote('\"Hi\"')}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hi", s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${unquote('Hi')}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hi", s);
+ }
+
@Test
public void testTrim() {
exchange.getMessage().setBody(" Hello World ");
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
index d0bcc1d061db..1eb3f9fa66af 100644
---
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
+++
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.spi;
+import java.util.Set;
+
import org.apache.camel.Expression;
/**
@@ -46,6 +48,11 @@ public interface SimpleFunctionRegistry {
*/
Expression getFunction(String name);
+ /**
+ * Returns a set with all the function names
+ */
+ Set<String> getFunctionNames();
+
/**
* Number of custom functions
*/
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
index 33030f5471de..d7d414081325 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
@@ -17,6 +17,7 @@
package org.apache.camel.impl.engine;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.camel.CamelContext;
@@ -53,6 +54,11 @@ public class DefaultSimpleFunctionRegistry extends
ServiceSupport implements Sim
return functions.get(name);
}
+ @Override
+ public Set<String> getFunctionNames() {
+ return functions.keySet();
+ }
+
@Override
protected void doStop() throws Exception {
super.doShutdown();
diff --git
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
index aef6018b1262..4c7afb123c23 100644
---
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
+++
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
@@ -128,11 +128,12 @@
"trim(exp)": { "index": 101, "kind": "function", "displayName": "Trim",
"group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The trim function trims
the message body (or expression) by removing all leading and trailing white
spaces.", "ognl": false, "suffix": "}" },
"type:name.field": { "index": 102, "kind": "function", "displayName":
"Java Field Value", "group": "function", "label": "function", "required":
false, "javaType": "Object", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "To
refer to a type or field by its classname. To refer to a field, you can append
.FIELD_NAME. For example, you can refer to the constant field from Exchange as:
`org.apache.camel.Exchange.FILE_NAME`", [...]
"kindOfType(exp)": { "index": 103, "kind": "function", "displayName":
"Kind of Type", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "What kind of type is
the value (null,number,string,boolean,array,object)", "ognl": false, "suffix":
"}" },
- "uppercase(exp)": { "index": 104, "kind": "function", "displayName":
"Uppercase", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Uppercases the message
body (or expression)", "ognl": false, "suffix": "}" },
- "uuid(type)": { "index": 105, "kind": "function", "displayName": "Generate
UUID", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Returns a UUID using the
Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and
`simple` as the type. If no type is given, the default is used. It is also
possible to use a custom `Uuid [...]
- "variable.name": { "index": 106, "kind": "function", "displayName":
"Variable", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The variable with the
given name", "ognl": true, "suffix": "}" },
- "variableAs(key,type)": { "index": 107, "kind": "function", "displayName":
"Variable As", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Converts the variable
to the given type (classname).", "ognl": false, "suffix": "}" },
- "variables": { "index": 108, "kind": "function", "displayName":
"Variables", "group": "function", "label": "function", "required": false,
"javaType": "java.util.Map", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Returns all the variables from the current Exchange in a Map", "ognl": false,
"suffix": "}" },
- "xpath(input,exp)": { "index": 109, "kind": "function", "displayName":
"XPath", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "When working with XML
data, then this allows using the XPath language, for example, to extract data
from the message body (in XML format). This requires having camel-xpath JAR on
the classpath. For input (optiona [...]
+ "unquote(exp)": { "index": 104, "kind": "function", "displayName":
"Unquote", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Returns the message
body (or expression) with any leading\/ending quotes removed", "ognl": false,
"suffix": "}" },
+ "uppercase(exp)": { "index": 105, "kind": "function", "displayName":
"Uppercase", "group": "function", "label": "function", "required": false,
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Uppercases the message
body (or expression)", "ognl": false, "suffix": "}" },
+ "uuid(type)": { "index": 106, "kind": "function", "displayName": "Generate
UUID", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Returns a UUID using the
Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and
`simple` as the type. If no type is given, the default is used. It is also
possible to use a custom `Uuid [...]
+ "variable.name": { "index": 107, "kind": "function", "displayName":
"Variable", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The variable with the
given name", "ognl": true, "suffix": "}" },
+ "variableAs(key,type)": { "index": 108, "kind": "function", "displayName":
"Variable As", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Converts the variable
to the given type (classname).", "ognl": false, "suffix": "}" },
+ "variables": { "index": 109, "kind": "function", "displayName":
"Variables", "group": "function", "label": "function", "required": false,
"javaType": "java.util.Map", "prefix": "${", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Returns all the variables from the current Exchange in a Map", "ognl": false,
"suffix": "}" },
+ "xpath(input,exp)": { "index": 110, "kind": "function", "displayName":
"XPath", "group": "function", "label": "function", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "When working with XML
data, then this allows using the XPath language, for example, to extract data
from the message body (in XML format). This requires having camel-xpath JAR on
the classpath. For input (optiona [...]
}
}
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 49f4f33d2e5f..4a37b75a3a00 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
@@ -473,6 +473,8 @@ The generator supports different kinds `default`,
`classic`, `short`, `simple` a
|`substringBetween(exp,after,before` | `String` | Returns a substring of the
expression that are between before and after. Returns `null` if nothing comes
between.
|`trim()` | `String` | The trim function trims the message body by removing
all leading and trailing white spaces.
|`trim(exp)` | `String` | The trim function trims the expression by removing
all leading and trailing white spaces.
+|`unquote()` | `String` | Returns the message body with any leading/ending
quotes removed
+|`unquote(exp)` | `String` | Returns the expression with any leading/ending
quotes removed
|`quote()` | `String` | Returns the message body as a double-quoted string
|`quote(exp)` | `String` | Returns the expression as a double-quoted string
|`uppercase()` | `String` | Uppercases the message body
@@ -1299,8 +1301,6 @@ $init{
}init$
----
-NOTE: You cannot declare custom functions that call other function functions.
This is currently not supported.
-
The function will by default use the message body as the input, such that the
following:
[source,java]
@@ -1319,6 +1319,33 @@ simple("Incoming message: ${cleanUp(' Please clean
this text for me ')}
Here the parameter is a fixed `String` but you can also pass in other type of
value such as a number, boolean, or even a nested function.
+==== Calling custom functions from custom functions
+
+It is also possible to reuse custom functions from other functions using
`${function(name)}`, such shown:
+
+[source,text]
+----
+$init{
+ $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}
+ $count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()}
+}init$
+----
+
+Here the `$count` is declared as a function which calls the cleanUp function
and then counts the words via split and size functions.
+
+Instead of using `${function(name)}` you can also same syntax as the built-in
functions, as follows:
+
+[source,text]
+----
+$init{
+ $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}
+ $count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()}
+}init$
+----
+
+Notice how `${cleanUp()}` despite being a custom function is using the same
syntax as all the built-in functions that are being used, for example:
+ `${trim()}`.
+
== OGNL Expression Support
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 b156f359575e..3c38b5eeb3c8 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
@@ -1075,6 +1075,19 @@ public final class CSimpleHelper {
return value;
}
+ public static String unquote(Exchange exchange, Object value) {
+ String body;
+ if (value != null) {
+ body =
exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange,
value);
+ } else {
+ body = exchange.getMessage().getBody(String.class);
+ }
+ if (body != null) {
+ body = StringHelper.removeLeadingAndEndingQuotes(body);
+ }
+ return body;
+ }
+
public static String trim(Exchange exchange, Object value) {
String body;
if (value != null) {
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
index f41ae793f8a0..0f53f09b3f22 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
@@ -421,6 +421,10 @@ public final class SimpleConstants {
label = "function", javaType = "Object", displayName = "Kind of
Type")
public static final String KIND_OF_TYPE = "kindOfType(exp)";
+ @Metadata(description = "Returns the message body (or expression) with any
leading/ending quotes removed",
+ label = "function", javaType = "String")
+ public static final String UNQUOTE = "unquote(exp)";
+
@Metadata(description = "Uppercases the message body (or expression)",
label = "function", javaType = "String",
displayName = "Uppercase")
public static final String UPPERCASE = "uppercase(exp)";
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 9d3cb2eff36a..3279a159d18f 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
@@ -686,14 +686,13 @@ public final class SimpleExpressionBuilder {
} else if (value instanceof CharSequence) {
return "string";
} else if (ObjectHelper.isPrimitiveArrayType(type) || value
instanceof Collection
- || value instanceof Map<?, ?>) {
+ || value instanceof Map<?, ?>) {
return "array";
} else {
return "object";
}
}
-
@Override
public String toString() {
if (expression != null) {
@@ -746,6 +745,46 @@ public final class SimpleExpressionBuilder {
};
}
+ /**
+ * Un quotes the given expressions (uses message body if expression is
null)
+ */
+ public static Expression unquoteExpression(final String expression) {
+ return new ExpressionAdapter() {
+ private Expression exp;
+
+ @Override
+ public void init(CamelContext context) {
+ if (expression != null) {
+ exp =
context.resolveLanguage("simple").createExpression(expression);
+ exp.init(context);
+ }
+ }
+
+ @Override
+ public Object evaluate(Exchange exchange) {
+ String value;
+ if (exp != null) {
+ value = exp.evaluate(exchange, String.class);
+ } else {
+ value = exchange.getMessage().getBody(String.class);
+ }
+ if (value != null) {
+ value = StringHelper.removeLeadingAndEndingQuotes(value);
+ }
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ if (expression != null) {
+ return "unquote(" + expression + ")";
+ } else {
+ return "unquote()";
+ }
+ }
+ };
+ }
+
/**
* Trims the given expressions (uses message body if expression is null)
*/
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
index fcbcb64d6977..01e752c063a5 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.simple.ast;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
@@ -28,6 +29,7 @@ import
org.apache.camel.language.simple.types.ChainOperatorType;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.PluginHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
@@ -83,47 +85,22 @@ public class ChainExpression extends BaseSimpleNode {
ObjectHelper.notNull(left, "left node", this);
ObjectHelper.notNull(right, "right node", this);
- // the expression parser does not parse literal text into
single/double quote tokens
- // so we need to manually remove leading quotes from the literal text
when using the other operators
+ // prepare nodes as the chain may refer to other functions
+ AtomicBoolean param = new AtomicBoolean();
+ left = prepareChainNode(camelContext, left, param);
final Expression leftExp = left.createExpression(camelContext,
expression);
final List<Expression> rightExp = new ArrayList<>();
for (SimpleNode rn : right) {
- boolean param = false;
- if (rn instanceof LiteralExpression le) {
- String text = le.getText();
- String changed =
StringHelper.removeLeadingAndEndingQuotes(text);
- if (changed.contains("$param")) {
- changed = changed.replace("$param", "${variable." +
CHAIN_VARIABLE + "}");
- param = true;
- }
- if (!changed.equals(text)) {
- le.replaceText(changed);
- }
- } else if (rn instanceof SimpleFunctionStart sf) {
- for (var child : sf.getBlock().getChildren()) {
- if (child instanceof LiteralExpression le) {
- String text = le.getText();
- String changed =
StringHelper.removeLeadingAndEndingQuotes(text);
- if (changed.contains("$param")) {
- changed = changed.replace("$param", "${variable."
+ CHAIN_VARIABLE + "}");
- param = true;
- }
- if (!changed.equals(text)) {
- le.replaceText(changed);
- }
- }
- }
- }
- final Expression exp = rn.createExpression(camelContext,
expression);
- Expression target = exp;
- if (param) {
- target = new ExpressionAdapter() {
+ rn = prepareChainNode(camelContext, rn, param);
+ final Expression right = rn.createExpression(camelContext,
expression);
+ if (param.get()) {
+ rightExp.add(new ExpressionAdapter() {
@Override
public Object evaluate(Exchange exchange) {
exchange.setVariable(CHAIN_VARIABLE,
exchange.getMessage().getBody());
try {
- return exp.evaluate(exchange, Object.class);
+ return right.evaluate(exchange, Object.class);
} finally {
exchange.removeVariable(CHAIN_VARIABLE);
}
@@ -131,11 +108,12 @@ public class ChainExpression extends BaseSimpleNode {
@Override
public String toString() {
- return exp.toString();
+ return right.toString();
}
- };
+ });
+ } else {
+ rightExp.add(right);
}
- rightExp.add(target);
}
if (operator == ChainOperatorType.CHAIN || operator ==
ChainOperatorType.CHAIN_NULL_SAFE) {
@@ -146,6 +124,48 @@ public class ChainExpression extends BaseSimpleNode {
throw new SimpleParserException("Unknown chain operator " + operator,
token.getIndex());
}
+ private SimpleNode prepareChainNode(CamelContext camelContext, SimpleNode
node, AtomicBoolean param) {
+ param.set(false);
+ if (node instanceof LiteralExpression le) {
+ String text = le.getText();
+ if (text.startsWith("$")) {
+ // this may be a function
+ String key = text.substring(1);
+ key = StringHelper.before(key, "(", key);
+ if
(PluginHelper.getSimpleFunctionRegistry(camelContext).getFunctionNames().contains(key))
{
+ String changed = text.replace("$" + key + "()",
"function(" + key + ")");
+ if (changed.equals(text)) {
+ changed = text.replace("$" + key + "(", "function(" +
key);
+ }
+ le.replaceText(changed);
+
+ SimpleFunctionStart sfs = new
SimpleFunctionStart(le.getToken(), null, true);
+ sfs.getBlock().addChild(le);
+ node = sfs;
+ }
+ }
+ text = le.getText();
+ if (text.contains("$param")) {
+ text = text.replace("$param", "${variable." + CHAIN_VARIABLE +
"}");
+ param.set(true);
+ le.replaceText(text);
+ }
+ return node;
+ } else if (node instanceof SimpleFunctionStart sf) {
+ for (var child : sf.getBlock().getChildren()) {
+ if (child instanceof LiteralExpression le) {
+ String text = le.getText();
+ if (text.contains("$param")) {
+ text = text.replace("$param", "${variable." +
CHAIN_VARIABLE + "}");
+ param.set(true);
+ le.replaceText(text);
+ }
+ }
+ }
+ }
+ return node;
+ }
+
private Expression createChainExpression(
final CamelContext camelContext, final Expression leftExp, final
List<Expression> rightExp, boolean nullSafe) {
return new Expression() {
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 52f2cbf77279..b53b4a9f6899 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
@@ -35,6 +35,7 @@ import
org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
import org.apache.camel.spi.Language;
import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.ResolverHelper;
import org.apache.camel.support.builder.ExpressionBuilder;
import org.apache.camel.util.ObjectHelper;
@@ -370,6 +371,21 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
}
+ // it may be a custom function
+ String name = StringHelper.before(function, "(", function);
+ if
(PluginHelper.getSimpleFunctionRegistry(camelContext).getFunctionNames().contains(name))
{
+ String after = StringHelper.after(function, "(");
+ if (after.equals(")")) {
+ function = "function(" + name + ")";
+ } else {
+ function = "function(" + name + "," + after;
+ }
+ Expression exp = createSimpleCustomFunction(camelContext,
function, strict);
+ if (exp != null) {
+ return exp;
+ }
+ }
+
if (strict) {
throw new SimpleParserException("Unknown function: " + function,
token.getIndex());
} else {
@@ -1214,6 +1230,16 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
return SimpleExpressionBuilder.safeQuoteExpression(exp);
}
+ // unquote function
+ remainder = ifStartsWithReturnRemainder("unquote(", function);
+ if (remainder != null) {
+ String exp = null;
+ String value = StringHelper.beforeLast(remainder, ")");
+ if (ObjectHelper.isNotEmpty(value)) {
+ exp = StringHelper.removeQuotes(value);
+ }
+ return SimpleExpressionBuilder.unquoteExpression(exp);
+ }
// trim function
remainder = ifStartsWithReturnRemainder("trim(", function);
@@ -3011,6 +3037,33 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
return "Object o = " + exp + ";\n return
safeQuote(exchange, o);";
}
+ // unquote function
+ remainder = ifStartsWithReturnRemainder("unquote(", function);
+ if (remainder != null) {
+ String exp = null;
+ String values = StringHelper.beforeLast(remainder, ")");
+ if (ObjectHelper.isNotEmpty(values)) {
+ String[] tokens = codeSplitSafe(values, ',', true, true);
+ if (tokens.length != 1) {
+ throw new SimpleParserException(
+ "Valid syntax: ${unquote(exp)} was: " + function,
token.getIndex());
+ }
+ // single quotes should be double quotes
+ String s = tokens[0];
+ if (StringHelper.isSingleQuoted(s)) {
+ s = StringHelper.removeLeadingAndEndingQuotes(s);
+ // need to escape double quotes
+ s = s.replace("\"", "\\\"");
+ // and enclose in a string
+ s = StringQuoteHelper.doubleQuote(s);
+ }
+ exp = s;
+ }
+ if (ObjectHelper.isEmpty(exp)) {
+ exp = "null";
+ }
+ return "Object o = " + exp + ";\n return unquote(exchange,
o);";
+ }
// trim function
remainder = ifStartsWithReturnRemainder("trim(", function);
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockChainTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockChainTest.java
deleted file mode 100644
index 48011db0e916..000000000000
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockChainTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.camel.language.simple;
-
-import org.apache.camel.LanguageTestSupport;
-import org.junit.jupiter.api.Test;
-
-public class SimpleInitBlockChainTest extends LanguageTestSupport {
-
- private static final String INIT = """
- $init{
- $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
- }init$
- You said: $clean()
- """;
-
- private static final String INIT2 = """
- $init{
- $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
- $count ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()} ~> ${split(' ')} ~> ${size()}
- }init$
- You said: $clean() in $count() words
- """;
-
- private static final String INIT3 = """
- $init{
- $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
- }init$
- You said: ${clean(' Clean this text please ... ')} and then
do something else
- """;
-
- @Test
- public void testInitBlockChain() throws Exception {
- exchange.getMessage().setBody(" Hello big World ");
-
- assertExpression(exchange, INIT, "You said: HELLO BIG WORLD\n");
- }
-
- @Test
- public void testInitBlockChain2() throws Exception {
- exchange.getMessage().setBody(" Hello big World ");
-
- assertExpression(exchange, INIT2, "You said: HELLO BIG WORLD in 3
words\n");
- }
-
- @Test
- public void testInitBlockChain3() throws Exception {
- exchange.getMessage().setBody("Hello World");
-
- assertExpression(exchange, INIT3, "You said: CLEAN THIS TEXT PLEASE
... and then do something else\n");
- }
-
- @Override
- protected String getLanguageName() {
- return "simple";
- }
-}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java
new file mode 100644
index 000000000000..2194182e2354
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import org.apache.camel.LanguageTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class SimpleInitBlockFunctionTest extends LanguageTestSupport {
+
+ private static final String INIT1 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ }init$
+ You said: $clean()
+ """;
+
+ private static final String INIT2 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ $count ~:= ${function(clean)} ~> ${split(' ')} ~> ${size()}
+ }init$
+ You said: $clean() in $count() words
+ """;
+
+ private static final String INIT3 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ }init$
+ You said: ${clean(' Clean this text please ... ')} and then
do something else
+ """;
+
+ private static final String INIT4 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ $count ~:= ${quote()} ~> ${function(clean)} ~> ${unquote()} ~>
${trim()} ~> ${split(' ')} ~> ${size()}
+ }init$
+ You said: $clean() in $count() words
+ """;
+
+ private static final String INIT5 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ $count ~:= $clean() ~> ${split(' ')} ~> ${size()}
+ }init$
+ You said: $clean() in $count() words
+ """;
+
+ private static final String INIT6 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ $count ~:= ${quote()} ~> ${clean()} ~> ${unquote()} ~> ${trim()}
~> ${split(' ')} ~> ${size()}
+ }init$
+ You said: $clean() in $count() words
+ """;
+
+ private static final String INIT7 = """
+ $init{
+ $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~>
${uppercase()}
+ }init$
+ You said: ${clean()}
+ """;
+
+ @Test
+ public void testInitBlockChain1() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT1, "You said: HELLO BIG WORLD\n");
+ }
+
+ @Test
+ public void testInitBlockChain2() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT2, "You said: HELLO BIG WORLD in 3
words\n");
+ }
+
+ @Test
+ public void testInitBlockChain3() throws Exception {
+ exchange.getMessage().setBody("Hello World");
+
+ assertExpression(exchange, INIT3, "You said: CLEAN THIS TEXT PLEASE
... and then do something else\n");
+ }
+
+ @Test
+ public void testInitBlockChain4() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT4, "You said: HELLO BIG WORLD in 3
words\n");
+ }
+
+ @Test
+ public void testInitBlockChain5() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT5, "You said: HELLO BIG WORLD in 3
words\n");
+ }
+
+ @Test
+ public void testInitBlockChain6() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT6, "You said: HELLO BIG WORLD in 3
words\n");
+ }
+
+ @Test
+ public void testInitBlockChain7() throws Exception {
+ exchange.getMessage().setBody(" Hello big World ");
+
+ assertExpression(exchange, INIT7, "You said: HELLO BIG WORLD\n");
+ }
+
+ @Override
+ protected String getLanguageName() {
+ return "simple";
+ }
+}
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 0e5e5d8fdd5a..ea9446f48d5e 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
@@ -3007,6 +3007,27 @@ public class SimpleTest extends LanguageTestSupport {
assertEquals("\"{A=1, B=2}\"", s);
}
+ @Test
+ public void testUnquote() {
+ exchange.getMessage().setBody("\"Hello World\"");
+
+ Expression expression =
context.resolveLanguage("simple").createExpression("${unquote()}");
+ String s = expression.evaluate(exchange, String.class);
+ assertEquals("Hello World", s);
+
+ expression =
context.resolveLanguage("simple").createExpression("${unquote(${body})}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hello World", s);
+
+ expression =
context.resolveLanguage("simple").createExpression("${unquote('\"Hi\"')}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hi", s);
+
+ expression =
context.resolveLanguage("simple").createExpression("${unquote('Hi')}");
+ s = expression.evaluate(exchange, String.class);
+ assertEquals("Hi", s);
+ }
+
@Test
public void testTrim() {
exchange.getMessage().setBody(" Hello World ");