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 03a8eb855513 CAMEL-22894: Extract jq/jsonpath/xpath/simpleJsonpath 
Simple functions into QueryLanguageFunctionFactory
03a8eb855513 is described below

commit 03a8eb8555132d83f49c88de2cae672124e52600
Author: Adriano Machado <[email protected]>
AuthorDate: Wed May 27 12:01:00 2026 -0400

    CAMEL-22894: Extract jq/jsonpath/xpath/simpleJsonpath Simple functions into 
QueryLanguageFunctionFactory
    
    Extract the four query language functions (jq, jsonpath, xpath, 
simpleJsonpath) from
    SimpleFunctionExpression.createSimpleCustomLanguage() into a new 
QueryLanguageFunctionFactory,
    registered in SimpleFunctionDispatcher.BUILT_INS. Also removes dead code in
    PropertiesFunctionFactory.createCode() where a parts.length > 2 guard could 
never be true.
    
    Closes #23566
---
 .../language/simple/SimpleFunctionDispatcher.java  |   2 +
 .../simple/ast/SimpleFunctionExpression.java       |  99 ------------------
 .../functions/PropertiesFunctionFactory.java       |   4 -
 .../functions/QueryLanguageFunctionFactory.java    | 111 ++++++++++++++++++++
 .../QueryLanguageFunctionFactoryTest.java          | 116 +++++++++++++++++++++
 5 files changed, 229 insertions(+), 103 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
index 3f5c2256a6a4..f4c9af9cbbd4 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
@@ -32,6 +32,7 @@ import 
org.apache.camel.language.simple.functions.MessageFunctionFactory;
 import org.apache.camel.language.simple.functions.MiscFunctionFactory;
 import org.apache.camel.language.simple.functions.OutputFunctionFactory;
 import org.apache.camel.language.simple.functions.PropertiesFunctionFactory;
+import org.apache.camel.language.simple.functions.QueryLanguageFunctionFactory;
 import org.apache.camel.language.simple.functions.RandomFunctionFactory;
 import org.apache.camel.language.simple.functions.SkipFunctionFactory;
 import org.apache.camel.language.simple.functions.StringFunctionFactory;
@@ -71,6 +72,7 @@ public final class SimpleFunctionDispatcher {
             new MathFunctionFactory(),
             new StringFunctionFactory(),
             new CollectionFunctionFactory(),
+            new QueryLanguageFunctionFactory(),
             new MiscFunctionFactory(),
             new SystemFunctionFactory(),
             new PropertiesFunctionFactory(),
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 408897ceff38..ec5b0ea65697 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
@@ -118,12 +118,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return answer;
         }
 
-        // custom languages
-        answer = createSimpleCustomLanguage(function, strict);
-        if (answer != null) {
-            return answer;
-        }
-
         // camelContext OGNL
         String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
         if (remainder != null) {
@@ -317,99 +311,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
-    private Expression createSimpleCustomLanguage(String function, boolean 
strict) {
-        // jq
-        String remainder = ifStartsWithReturnRemainder("jq(", function);
-        if (remainder != null) {
-            String exp = StringHelper.beforeLast(remainder, ")");
-            if (exp == null) {
-                throw new SimpleParserException("Valid syntax: ${jq(exp)} was: 
" + function, token.getIndex());
-            }
-            exp = StringHelper.removeLeadingAndEndingQuotes(exp);
-            if (exp.startsWith("header:") || exp.startsWith("property:") || 
exp.startsWith("exchangeProperty:")
-                    || exp.startsWith("variable:")) {
-                String input = StringHelper.before(exp, ",");
-                exp = StringHelper.after(exp, ",");
-                if (input != null) {
-                    input = input.trim();
-                }
-                if (exp != null) {
-                    exp = exp.trim();
-                }
-                return ExpressionBuilder.singleInputLanguageExpression("jq", 
exp, input);
-            }
-            return ExpressionBuilder.languageExpression("jq", exp);
-        }
-        // simple-jsonpath
-        remainder = ifStartsWithReturnRemainder("simpleJsonpath(", function);
-        if (remainder != null) {
-            String exp = StringHelper.beforeLast(remainder, ")");
-            if (exp == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${simpleJsonpath(exp)} was: " + 
function, token.getIndex());
-            }
-            String input = null;
-            exp = StringHelper.removeLeadingAndEndingQuotes(exp);
-            if (exp.startsWith("header:") || exp.startsWith("property:") || 
exp.startsWith("exchangeProperty:")
-                    || exp.startsWith("variable:")) {
-                input = StringHelper.before(exp, ",");
-                exp = StringHelper.after(exp, ",");
-                if (input != null) {
-                    input = input.trim();
-                }
-                if (exp != null) {
-                    exp = exp.trim();
-                }
-            }
-            return SimpleExpressionBuilder.simpleJsonPathExpression(input, 
exp);
-        }
-        // jsonpath
-        remainder = ifStartsWithReturnRemainder("jsonpath(", function);
-        if (remainder != null) {
-            String exp = StringHelper.beforeLast(remainder, ")");
-            if (exp == null) {
-                throw new SimpleParserException("Valid syntax: 
${jsonpath(exp)} was: " + function, token.getIndex());
-            }
-            exp = StringHelper.removeLeadingAndEndingQuotes(exp);
-            if (exp.startsWith("header:") || exp.startsWith("property:") || 
exp.startsWith("exchangeProperty:")
-                    || exp.startsWith("variable:")) {
-                String input = StringHelper.before(exp, ",");
-                exp = StringHelper.after(exp, ",");
-                if (input != null) {
-                    input = input.trim();
-                }
-                if (exp != null) {
-                    exp = exp.trim();
-                }
-                return 
ExpressionBuilder.singleInputLanguageExpression("jsonpath", exp, input);
-            }
-            return ExpressionBuilder.languageExpression("jsonpath", exp);
-        }
-        remainder = ifStartsWithReturnRemainder("xpath(", function);
-        if (remainder != null) {
-            String exp = StringHelper.beforeLast(remainder, ")");
-            if (exp == null) {
-                throw new SimpleParserException("Valid syntax: ${xpath(exp)} 
was: " + function, token.getIndex());
-            }
-            exp = StringHelper.removeLeadingAndEndingQuotes(exp);
-            if (exp.startsWith("header:") || exp.startsWith("property:") || 
exp.startsWith("exchangeProperty:")
-                    || exp.startsWith("variable:")) {
-                String input = StringHelper.before(exp, ",");
-                exp = StringHelper.after(exp, ",");
-                if (input != null) {
-                    input = input.trim();
-                }
-                if (exp != null) {
-                    exp = exp.trim();
-                }
-                return 
ExpressionBuilder.singleInputLanguageExpression("xpath", exp, input);
-            }
-            return ExpressionBuilder.languageExpression("xpath", exp);
-        }
-
-        return null;
-    }
-
     private Expression createSimpleFileExpression(String remainder, boolean 
strict) {
         if (ObjectHelper.equal(remainder, "name")) {
             return SimpleExpressionBuilder.fileNameExpression();
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/PropertiesFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/PropertiesFunctionFactory.java
index ccbff2b776ce..6f1595b58b9a 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/PropertiesFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/PropertiesFunctionFactory.java
@@ -18,7 +18,6 @@ package org.apache.camel.language.simple.functions;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
-import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.builder.ExpressionBuilder;
 
@@ -64,9 +63,6 @@ public final class PropertiesFunctionFactory implements 
SimpleLanguageFunctionFa
         String remainder = ifStartsWithReturnRemainder("properties:", 
function);
         if (remainder != null) {
             String[] parts = remainder.split(":", 2);
-            if (parts.length > 2) {
-                throw new SimpleParserException("Valid syntax: 
${properties:key[:default]} was: " + function, index);
-            }
             String key = parts[0].trim();
             if (parts.length >= 2) {
                 return "properties(exchange, \"" + key + "\", \"" + 
parts[1].trim() + "\")";
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactory.java
new file mode 100644
index 000000000000..fe681c3a0c8c
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactory.java
@@ -0,0 +1,111 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.builder.ExpressionBuilder;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple functions that delegate evaluation to another Camel 
language: {@code ${jq(exp)}},
+ * {@code ${jsonpath(exp)}}, {@code ${xpath(exp)}}, {@code 
${simpleJsonpath(exp)}}.
+ * <p>
+ * All four functions support an optional input-source qualifier so the 
language operates on a header, exchange
+ * property, or variable rather than the body: e.g. {@code 
${jq(header:myHeader,.name)}}.
+ * <p>
+ * CSimple code generation is not supported for these functions (returns 
{@code null}).
+ */
+public final class QueryLanguageFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder;
+
+        remainder = ifStartsWithReturnRemainder("jq(", function);
+        if (remainder != null) {
+            return delegateLanguageExpression("jq", remainder, function, 
index);
+        }
+
+        remainder = ifStartsWithReturnRemainder("simpleJsonpath(", function);
+        if (remainder != null) {
+            return createSimpleJsonpathExpression(remainder, function, index);
+        }
+
+        remainder = ifStartsWithReturnRemainder("jsonpath(", function);
+        if (remainder != null) {
+            return delegateLanguageExpression("jsonpath", remainder, function, 
index);
+        }
+
+        remainder = ifStartsWithReturnRemainder("xpath(", function);
+        if (remainder != null) {
+            return delegateLanguageExpression("xpath", remainder, function, 
index);
+        }
+
+        return null;
+    }
+
+    private static Expression delegateLanguageExpression(String name, String 
remainder, String function, int index) {
+        String exp = StringHelper.beforeLast(remainder, ")");
+        if (exp == null) {
+            throw new SimpleParserException("Valid syntax: ${" + name + 
"(exp)} was: " + function, index);
+        }
+        exp = StringHelper.removeLeadingAndEndingQuotes(exp);
+        if (hasInputSource(exp)) {
+            String input = StringHelper.before(exp, ",");
+            exp = StringHelper.after(exp, ",");
+            if (input != null) {
+                input = input.trim();
+            }
+            if (exp != null) {
+                exp = exp.trim();
+            }
+            return ExpressionBuilder.singleInputLanguageExpression(name, exp, 
input);
+        }
+        return ExpressionBuilder.languageExpression(name, exp);
+    }
+
+    private static Expression createSimpleJsonpathExpression(String remainder, 
String function, int index) {
+        String exp = StringHelper.beforeLast(remainder, ")");
+        if (exp == null) {
+            throw new SimpleParserException("Valid syntax: 
${simpleJsonpath(exp)} was: " + function, index);
+        }
+        String input = null;
+        exp = StringHelper.removeLeadingAndEndingQuotes(exp);
+        if (hasInputSource(exp)) {
+            input = StringHelper.before(exp, ",");
+            exp = StringHelper.after(exp, ",");
+            if (input != null) {
+                input = input.trim();
+            }
+            if (exp != null) {
+                exp = exp.trim();
+            }
+        }
+        return SimpleExpressionBuilder.simpleJsonPathExpression(input, exp);
+    }
+
+    private static boolean hasInputSource(String exp) {
+        return exp.startsWith("header:") || exp.startsWith("property:")
+                || exp.startsWith("exchangeProperty:") || 
exp.startsWith("variable:");
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactoryTest.java
new file mode 100644
index 000000000000..6f49c5e11f85
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/QueryLanguageFunctionFactoryTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class QueryLanguageFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new QueryLanguageFunctionFactory();
+    }
+
+    // --- xpath ---
+
+    @Test
+    public void testXpathBody() {
+        exchange.getIn().setBody("<root><name>World</name></root>");
+        assertEquals("World", evaluate("xpath('/root/name/text()')", 
String.class));
+    }
+
+    @Test
+    public void testXpathWithHeaderInput() {
+        exchange.getIn().setHeader("myHdr", "<root><val>42</val></root>");
+        assertEquals("42", evaluate("xpath('header:myHdr,/root/val/text()')", 
String.class));
+    }
+
+    @Test
+    public void testXpathMissingClosingParen() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"xpath('unclosed", 0));
+    }
+
+    // --- jq / jsonpath / simpleJsonpath: factory creates expressions 
(languages not on classpath in core) ---
+
+    @Test
+    public void testJqCreatesFunctionExpression() {
+        Expression exp = createFactory().createFunction(context, 
"jq('.name')", 0);
+        assertNotNull(exp);
+    }
+
+    @Test
+    public void testJqWithHeaderInputCreatesExpression() {
+        Expression exp = createFactory().createFunction(context, 
"jq('header:myHdr,.name')", 0);
+        assertNotNull(exp);
+    }
+
+    @Test
+    public void testJqMissingClosingParen() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, "jq('unclosed", 
0));
+    }
+
+    @Test
+    public void testJsonpathCreatesExpression() {
+        Expression exp = createFactory().createFunction(context, 
"jsonpath('$.name')", 0);
+        assertNotNull(exp);
+    }
+
+    @Test
+    public void testJsonpathMissingClosingParen() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"jsonpath('unclosed", 0));
+    }
+
+    @Test
+    public void testSimpleJsonpathCreatesExpression() {
+        Expression exp = createFactory().createFunction(context, 
"simpleJsonpath('$.name')", 0);
+        assertNotNull(exp);
+    }
+
+    @Test
+    public void testSimpleJsonpathMissingClosingParen() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"simpleJsonpath('unclosed", 0));
+    }
+
+    // --- createCode returns null (no CSimple support) ---
+
+    @Test
+    public void testCreateCodeReturnsNull() {
+        assertNull(createFactory().createCode(context, "jq('.name')", 0));
+        assertNull(createFactory().createCode(context, "jsonpath('$.name')", 
0));
+        assertNull(createFactory().createCode(context, "xpath('/root')", 0));
+        assertNull(createFactory().createCode(context, 
"simpleJsonpath('$.name')", 0));
+    }
+
+    // --- unrecognized ---
+
+    @Test
+    public void testUnrecognizedFunction() {
+        assertNull(createFactory().createFunction(context, "unknown", 0));
+    }
+}

Reply via email to