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 e2cce97bc7ef CAMEL-22894: Extract OGNL/exchange-property and 
function(...) Simple functions into dedicated factories
e2cce97bc7ef is described below

commit e2cce97bc7eff2e649ae8c4a55fbdb6c82673de2
Author: Adriano Machado <[email protected]>
AuthorDate: Thu May 28 04:18:58 2026 -0400

    CAMEL-22894: Extract OGNL/exchange-property and function(...) Simple 
functions into dedicated factories
    
    Extract two groups of inline logic from SimpleFunctionExpression into new
    SimpleLanguageFunctionFactory implementations: ExchangeFunctionFactory
    (camelContext OGNL, exception OGNL, exceptionAs, exchangeProperty,
    exchangePropertyAs, exchangePropertyAsIndex, exchange OGNL) and
    CustomFunctionFactory (function(name) and function(name, exp)). This
    continues the ongoing extraction effort and reduces SimpleFunctionExpression
    from ~560 to 295 lines. Also fixes an incorrect error message in the
    exception OGNL createCode path.
    
    Closes #23576
---
 .../language/simple/SimpleFunctionDispatcher.java  |   4 +
 .../simple/ast/SimpleFunctionExpression.java       | 276 +--------------------
 .../simple/functions/CustomFunctionFactory.java    |  71 ++++++
 .../simple/functions/ExchangeFunctionFactory.java  | 262 +++++++++++++++++++
 .../functions/CustomFunctionFactoryTest.java       | 104 ++++++++
 .../functions/ExchangeFunctionFactoryTest.java     | 155 ++++++++++++
 6 files changed, 600 insertions(+), 272 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 b432af0e94d4..3c22f8fc3549 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
@@ -25,7 +25,9 @@ import 
org.apache.camel.language.simple.functions.BeanFunctionFactory;
 import org.apache.camel.language.simple.functions.BodyFunctionFactory;
 import org.apache.camel.language.simple.functions.CollateFunctionFactory;
 import org.apache.camel.language.simple.functions.CollectionFunctionFactory;
+import org.apache.camel.language.simple.functions.CustomFunctionFactory;
 import org.apache.camel.language.simple.functions.DateFunctionFactory;
+import org.apache.camel.language.simple.functions.ExchangeFunctionFactory;
 import org.apache.camel.language.simple.functions.HeaderFunctionFactory;
 import org.apache.camel.language.simple.functions.JoinFunctionFactory;
 import org.apache.camel.language.simple.functions.MathFunctionFactory;
@@ -66,6 +68,8 @@ public final class SimpleFunctionDispatcher {
             new BodyFunctionFactory(),
             new HeaderFunctionFactory(),
             new VariableFunctionFactory(),
+            new ExchangeFunctionFactory(),
+            new CustomFunctionFactory(),
             new RandomFunctionFactory(),
             new SkipFunctionFactory(),
             new CollateFunctionFactory(),
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 43fab4c55d7b..5ee6064093e7 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
@@ -31,9 +31,7 @@ import org.apache.camel.language.simple.types.SimpleToken;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.OgnlHelper;
 import org.apache.camel.util.StringHelper;
-import org.apache.camel.util.StringQuoteHelper;
 
 /**
  * Represents one of built-in functions of the <a 
href="http://camel.apache.org/simple.html";>simple language</a>
@@ -108,71 +106,8 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return answer;
         }
 
-        // custom functions
-        answer = createSimpleCustomFunction(camelContext, function, strict);
-        if (answer != null) {
-            return answer;
-        }
-
-        // camelContext OGNL
-        String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${camelContext.OGNL} was: " + function, token.getIndex());
-            }
-            return 
SimpleExpressionBuilder.camelContextOgnlExpression(remainder);
-        }
-
-        // Exception OGNL
-        remainder = ifStartsWithReturnRemainder("exception", function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exception.OGNL} was: " + function, token.getIndex());
-            }
-            return 
SimpleExpressionBuilder.exchangeExceptionOgnlExpression(remainder);
-        }
-
-        // exchange property
-        remainder = ifStartsWithReturnRemainder("exchangeProperty", 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);
-            }
-
-            // validate syntax
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exchangeProperty.OGNL} was: " + function, token.getIndex());
-            }
-
-            if (OgnlHelper.isValidOgnlExpression(remainder)) {
-                // ognl based property
-                return 
SimpleExpressionBuilder.propertyOgnlExpression(remainder);
-            } else {
-                // regular property
-                return ExpressionBuilder.exchangePropertyExpression(remainder);
-            }
-        }
-
-        // exchange OGNL
-        remainder = ifStartsWithReturnRemainder("exchange", function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exchange.OGNL} was: " + function, token.getIndex());
-            }
-            return SimpleExpressionBuilder.exchangeOgnlExpression(remainder);
-        }
-
         // file: prefix
-        remainder = ifStartsWithReturnRemainder("file:", function);
+        String remainder = ifStartsWithReturnRemainder("file:", function);
         if (remainder != null) {
             Expression fileExpression;
             if (skipFileFunctions) {
@@ -198,7 +133,7 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return external;
         }
 
-        // it may be a custom function
+        // it may be a custom function registered without the function(...) 
wrapper
         String name = StringHelper.before(function, "(", function);
         if 
(PluginHelper.getSimpleFunctionRegistry(camelContext).getFunction(name) != 
null) {
             String after = StringHelper.after(function, "(");
@@ -207,7 +142,7 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             } else {
                 function = "function(" + name + "," + after;
             }
-            Expression exp = createSimpleCustomFunction(camelContext, 
function, strict);
+            Expression exp = 
SimpleFunctionDispatcher.tryCreateBuiltIn(camelContext, function, 
token.getIndex());
             if (exp != null) {
                 return exp;
             }
@@ -220,38 +155,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         }
     }
 
-    private Expression createSimpleCustomFunction(CamelContext camelContext, 
String function, boolean strict) {
-        String remainder = ifStartsWithReturnRemainder("function(", function);
-        if (remainder != null) {
-            String key;
-            String param = null;
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${function(name)} or 
${function(name,exp)} was: " + function,
-                        token.getIndex());
-            }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
true, true);
-            if (tokens.length < 1 || tokens.length > 2) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${function(name)} or 
${function(name,exp)} was: " + function,
-                        token.getIndex());
-            }
-            key = StringHelper.removeQuotes(tokens[0]);
-            key = key.trim();
-            if (tokens.length == 2) {
-                param = tokens[1];
-                param = 
StringHelper.removeLeadingAndEndingQuotes(param.trim());
-            }
-            if (param == null) {
-                param = "${body}";
-            }
-            return SimpleExpressionBuilder.customFunction(key, param);
-        }
-
-        return null;
-    }
-
     private Expression createSimpleFileExpression(String remainder, boolean 
strict) {
         if (ObjectHelper.equal(remainder, "name")) {
             return SimpleExpressionBuilder.fileNameExpression();
@@ -307,58 +210,8 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return answer;
         }
 
-        // exchange property first
-        answer = createCodeExchangeProperty(function);
-        if (answer != null) {
-            return answer;
-        }
-        // camelContext OGNL
-        String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${camelContext.OGNL} was: " + function, token.getIndex());
-            }
-            return "context" + ognlCodeMethods(remainder, null);
-        }
-
-        // ExceptionAs OGNL
-        remainder = ifStartsWithReturnRemainder("exceptionAs(", function);
-        if (remainder != null) {
-            String type = StringHelper.before(remainder, ")");
-            remainder = StringHelper.after(remainder, ")");
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (type.isEmpty() || invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exceptionAs(type).OGNL} was: " + function, token.getIndex());
-            }
-            return "exceptionAs(exchange, " + type + ")" + 
ognlCodeMethods(remainder, type);
-        }
-
-        // Exception OGNL
-        remainder = ifStartsWithReturnRemainder("exception", function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exceptionAs(type).OGNL} was: " + function, token.getIndex());
-            }
-            return "exception(exchange)" + ognlCodeMethods(remainder, null);
-        }
-
-        // exchange OGNL
-        remainder = ifStartsWithReturnRemainder("exchange", function);
-        if (remainder != null) {
-            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${exchange.OGNL} was: " + function, token.getIndex());
-            }
-            return "exchange" + ognlCodeMethods(remainder, null);
-        }
-
         // file: prefix
-        remainder = ifStartsWithReturnRemainder("file:", function);
+        String remainder = ifStartsWithReturnRemainder("file:", function);
         if (remainder != null) {
             return createCodeFileExpression(remainder);
         }
@@ -395,127 +248,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         throw new SimpleParserException("Unknown function: " + function, 
token.getIndex());
     }
 
-    private String createCodeExchangeProperty(final String function) {
-        // exchangePropertyAsIndex
-        String remainder = 
ifStartsWithReturnRemainder("exchangePropertyAsIndex(", function);
-        if (remainder != null) {
-            String keyTypeAndIndex = StringHelper.before(remainder, ")");
-            if (keyTypeAndIndex == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, token.getIndex());
-            }
-            String[] parts = keyTypeAndIndex.split(",");
-            if (parts.length != 3) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, token.getIndex());
-            }
-            String key = parts[0];
-            String type = parts[1];
-            String index = parts[2];
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isEmpty(index)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            key = key.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            index = StringHelper.removeQuotes(index);
-            index = index.trim();
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${exchangePropertyAsIndex(key, 
type, index).OGNL} was: " + function,
-                            token.getIndex());
-                }
-                return "exchangePropertyAsIndex(exchange, " + type + ", \"" + 
key + "\", \"" + index + "\")"
-                       + ognlCodeMethods(remainder, type);
-            } else {
-                return "exchangePropertyAsIndex(exchange, " + type + ", \"" + 
key + "\", \"" + index + "\")";
-            }
-        }
-
-        // exchangePropertyAs
-        remainder = ifStartsWithReturnRemainder("exchangePropertyAs(", 
function);
-        if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
-            if (keyAndType == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAs(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)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAs(key, type)} was: " 
+ function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            key = key.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            return "exchangePropertyAs(exchange, \"" + key + "\", " + type + 
")" + ognlCodeMethods(remainder, type);
-        }
-
-        // exchange property
-        remainder = ifStartsWithReturnRemainder("exchangeProperty", function);
-        if (remainder != null) {
-            // remove leading character (dot or ?)
-            if (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);
-            key = key.trim();
-
-            // validate syntax
-            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
-            if (invalid) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangeProperty.name[key]} was: " + 
function, token.getIndex());
-            }
-
-            // it is an index?
-            String index = null;
-            if (key.endsWith("]")) {
-                index = StringHelper.between(key, "[", "]");
-                if (index != null) {
-                    key = StringHelper.before(key, "[");
-                }
-            }
-            if (index != null) {
-                index = StringHelper.removeLeadingAndEndingQuotes(index);
-                return "exchangePropertyAsIndex(exchange, Object.class, \"" + 
key + "\", \"" + index + "\")";
-            } else if (OgnlHelper.isValidOgnlExpression(remainder)) {
-                // ognl based exchange property must be typed
-                throw new SimpleParserException(
-                        "Valid syntax: ${exchangePropertyAs(key, type)} was: " 
+ function, token.getIndex());
-            } else {
-                // regular property
-                return "exchangeProperty(exchange, \"" + key + "\")";
-            }
-        }
-
-        return null;
-    }
-
-    private static String appendClass(String type) {
-        type = StringHelper.removeQuotes(type);
-        if (!type.endsWith(".class")) {
-            type = type + ".class";
-        }
-        return type;
-    }
-
     private String createCodeFileExpression(String remainder) {
         if (ObjectHelper.equal(remainder, "name")) {
             return "fileName(message)";
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CustomFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CustomFunctionFactory.java
new file mode 100644
index 000000000000..a98f278103a1
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CustomFunctionFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple function for user-defined custom functions: {@code 
${function(name)}} and
+ * {@code ${function(name,exp)}}.
+ */
+public final class CustomFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder = ifStartsWithReturnRemainder("function(", function);
+        if (remainder == null) {
+            return null;
+        }
+
+        String key;
+        String param = null;
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException(
+                    "Valid syntax: ${function(name)} or ${function(name,exp)} 
was: " + function, index);
+        }
+        String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', true, 
true);
+        if (tokens.length < 1 || tokens.length > 2) {
+            throw new SimpleParserException(
+                    "Valid syntax: ${function(name)} or ${function(name,exp)} 
was: " + function, index);
+        }
+        key = StringHelper.removeQuotes(tokens[0]);
+        key = key.trim();
+        if (tokens.length == 2) {
+            param = tokens[1];
+            param = StringHelper.removeLeadingAndEndingQuotes(param.trim());
+        }
+        if (param == null) {
+            param = "${body}";
+        }
+        return SimpleExpressionBuilder.customFunction(key, param);
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        return null;
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
new file mode 100644
index 000000000000..e6d9f2f88f40
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
@@ -0,0 +1,262 @@
+/*
+ * 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.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.appendClass;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ognlCodeMethods;
+
+/**
+ * Built-in Simple function for exchange-related OGNL navigation: {@code 
${camelContext.OGNL}},
+ * {@code ${exception.OGNL}}, {@code ${exceptionAs(type).OGNL}}, {@code 
${exchangeProperty.name}},
+ * {@code ${exchangePropertyAs(key,type)}}, {@code 
${exchangePropertyAsIndex(key,type,index)}},
+ * {@code ${exchange.OGNL}}.
+ */
+public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        // camelContext OGNL
+        String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${camelContext.OGNL} was: " + function, index);
+            }
+            return 
SimpleExpressionBuilder.camelContextOgnlExpression(remainder);
+        }
+
+        // Exception OGNL — exchangeProperty/exchange checked separately, no 
prefix clash here
+        remainder = ifStartsWithReturnRemainder("exception", function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exception.OGNL} was: " + function, index);
+            }
+            return 
SimpleExpressionBuilder.exchangeExceptionOgnlExpression(remainder);
+        }
+
+        // exchangeProperty must be checked before exchange to avoid prefix 
clash
+        remainder = ifStartsWithReturnRemainder("exchangeProperty", 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);
+            }
+
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exchangeProperty.OGNL} was: " + function, index);
+            }
+
+            if (OgnlHelper.isValidOgnlExpression(remainder)) {
+                return 
SimpleExpressionBuilder.propertyOgnlExpression(remainder);
+            } else {
+                return ExpressionBuilder.exchangePropertyExpression(remainder);
+            }
+        }
+
+        // exchange OGNL
+        remainder = ifStartsWithReturnRemainder("exchange", function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exchange.OGNL} was: " + function, index);
+            }
+            return SimpleExpressionBuilder.exchangeOgnlExpression(remainder);
+        }
+
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        // exchange property variants first to avoid prefix clash with 
exchange OGNL
+        String answer = createCodeExchangeProperty(function, index);
+        if (answer != null) {
+            return answer;
+        }
+
+        // camelContext OGNL
+        String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${camelContext.OGNL} was: " + function, index);
+            }
+            return "context" + ognlCodeMethods(remainder, null);
+        }
+
+        // exceptionAs must be checked before exception to avoid prefix clash
+        remainder = ifStartsWithReturnRemainder("exceptionAs(", function);
+        if (remainder != null) {
+            String type = StringHelper.before(remainder, ")");
+            remainder = StringHelper.after(remainder, ")");
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (type.isEmpty() || invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exceptionAs(type).OGNL} was: " + function, index);
+            }
+            return "exceptionAs(exchange, " + type + ")" + 
ognlCodeMethods(remainder, type);
+        }
+
+        // Exception OGNL
+        remainder = ifStartsWithReturnRemainder("exception", function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exception.OGNL} was: " + function, index);
+            }
+            return "exception(exchange)" + ognlCodeMethods(remainder, null);
+        }
+
+        // exchange OGNL
+        remainder = ifStartsWithReturnRemainder("exchange", function);
+        if (remainder != null) {
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${exchange.OGNL} was: " + function, index);
+            }
+            return "exchange" + ognlCodeMethods(remainder, null);
+        }
+
+        return null;
+    }
+
+    private static String createCodeExchangeProperty(String function, int 
index) {
+        // exchangePropertyAsIndex must be checked before exchangePropertyAs 
and exchangeProperty
+        String remainder = 
ifStartsWithReturnRemainder("exchangePropertyAsIndex(", function);
+        if (remainder != null) {
+            String keyTypeAndIndex = StringHelper.before(remainder, ")");
+            if (keyTypeAndIndex == null) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, index);
+            }
+            String[] parts = keyTypeAndIndex.split(",");
+            if (parts.length != 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, index);
+            }
+            String key = parts[0];
+            String type = parts[1];
+            String idx = parts[2];
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isEmpty(idx)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAsIndex(key, type, 
index)} was: " + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            idx = StringHelper.removeQuotes(idx);
+            idx = idx.trim();
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${exchangePropertyAsIndex(key, 
type, index).OGNL} was: " + function, index);
+                }
+                return "exchangePropertyAsIndex(exchange, " + type + ", \"" + 
key + "\", \"" + idx + "\")"
+                       + ognlCodeMethods(remainder, type);
+            } else {
+                return "exchangePropertyAsIndex(exchange, " + type + ", \"" + 
key + "\", \"" + idx + "\")";
+            }
+        }
+
+        // exchangePropertyAs must be checked before exchangeProperty
+        remainder = ifStartsWithReturnRemainder("exchangePropertyAs(", 
function);
+        if (remainder != null) {
+            String keyAndType = StringHelper.before(remainder, ")");
+            if (keyAndType == null) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAs(key, type)} was: " 
+ function, index);
+            }
+            String key = StringHelper.before(keyAndType, ",");
+            String type = StringHelper.after(keyAndType, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAs(key, type)} was: " 
+ function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            return "exchangePropertyAs(exchange, \"" + key + "\", " + type + 
")" + ognlCodeMethods(remainder, type);
+        }
+
+        // plain exchangeProperty
+        remainder = ifStartsWithReturnRemainder("exchangeProperty", function);
+        if (remainder != null) {
+            // remove leading character (dot or ?)
+            if (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);
+            }
+            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
+            key = key.trim();
+
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
+            if (invalid) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangeProperty.name[key]} was: " + 
function, index);
+            }
+
+            String idx = null;
+            if (key.endsWith("]")) {
+                idx = StringHelper.between(key, "[", "]");
+                if (idx != null) {
+                    key = StringHelper.before(key, "[");
+                }
+            }
+            if (idx != null) {
+                idx = StringHelper.removeLeadingAndEndingQuotes(idx);
+                return "exchangePropertyAsIndex(exchange, Object.class, \"" + 
key + "\", \"" + idx + "\")";
+            } else if (OgnlHelper.isValidOgnlExpression(remainder)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${exchangePropertyAs(key, type)} was: " 
+ function, index);
+            } else {
+                return "exchangeProperty(exchange, \"" + key + "\")";
+            }
+        }
+
+        return null;
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CustomFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CustomFunctionFactoryTest.java
new file mode 100644
index 000000000000..47aeea527b7d
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CustomFunctionFactoryTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Exchange;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleFunction;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class CustomFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @BeforeEach
+    public void registerFunction() {
+        PluginHelper.getSimpleFunctionRegistry(context).addFunction(new 
SimpleFunction() {
+            @Override
+            public String getName() {
+                return "shout";
+            }
+
+            @Override
+            public Object apply(Exchange exchange, Object input) {
+                return input.toString().toUpperCase() + "!";
+            }
+        });
+    }
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new CustomFunctionFactory();
+    }
+
+    // --- function(name) with default body input ---
+
+    @Test
+    public void testFunctionWithBody() {
+        exchange.getIn().setBody("hello");
+        assertEquals("HELLO!", evaluate("function(shout)", String.class));
+    }
+
+    // --- function(name, exp) with explicit expression ---
+
+    @Test
+    public void testFunctionWithExplicitExp() {
+        exchange.getIn().setHeader("msg", "world");
+        assertEquals("WORLD!", evaluate("function(shout,${header.msg})", 
String.class));
+    }
+
+    // --- bare name syntax (look-ahead path in SimpleFunctionExpression) ---
+
+    @Test
+    public void testBareNameSyntax() {
+        exchange.getIn().setBody("camel");
+        assertEquals("CAMEL!", evaluate("shout", String.class));
+    }
+
+    // --- error cases ---
+
+    @Test
+    public void testMissingClosingParen() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"function(shout", 0));
+    }
+
+    @Test
+    public void testEmptyName() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, "function()", 
0));
+    }
+
+    // --- createCode always returns null (no CSimple support) ---
+
+    @Test
+    public void testCreateCodeReturnsNull() {
+        assertNull(createFactory().createCode(context, "function(shout)", 0));
+    }
+
+    // --- unrecognized ---
+
+    @Test
+    public void testUnrecognizedFunction() {
+        assertNull(createFactory().createFunction(context, "unknown", 0));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactoryTest.java
new file mode 100644
index 000000000000..1a5947be5fdf
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactoryTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ExchangeFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new ExchangeFunctionFactory();
+    }
+
+    // --- camelContext ---
+
+    @Test
+    public void testCamelContextVersion() {
+        String version = evaluate("camelContext.version", String.class);
+        assertEquals(context.getVersion(), version);
+    }
+
+    @Test
+    public void testCamelContextInvalidOgnl() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"camelContext[bad", 0));
+    }
+
+    // --- exception ---
+
+    @Test
+    public void testExceptionMessage() {
+        exchange.setException(new IllegalArgumentException("oops"));
+        assertEquals("oops", evaluate("exception.message", String.class));
+    }
+
+    @Test
+    public void testExceptionInvalidOgnl() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, "exception[bad", 
0));
+    }
+
+    // --- exchangeProperty ---
+
+    @Test
+    public void testExchangePropertyDotNotation() {
+        exchange.setProperty("color", "blue");
+        assertEquals("blue", evaluate("exchangeProperty.color", String.class));
+    }
+
+    @Test
+    public void testExchangePropertyColonNotation() {
+        exchange.setProperty("color", "red");
+        assertEquals("red", evaluate("exchangeProperty:color", String.class));
+    }
+
+    @Test
+    public void testExchangePropertyBracketNotation() {
+        exchange.setProperty("color", "green");
+        assertEquals("green", evaluate("exchangeProperty[color]", 
String.class));
+    }
+
+    @Test
+    public void testExchangePropertyInvalidOgnl() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, 
"exchangeProperty.foobar[bar", 0));
+    }
+
+    // --- exchange OGNL ---
+
+    @Test
+    public void testExchangeExchangeId() {
+        String id = evaluate("exchange.exchangeId", String.class);
+        assertEquals(exchange.getExchangeId(), id);
+    }
+
+    @Test
+    public void testExchangeInvalidOgnl() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createFunction(context, "exchange[bad", 
0));
+    }
+
+    // --- createCode ---
+
+    @Test
+    public void testCreateCodeCamelContext() {
+        assertEquals("context.getName()", createCode("camelContext.name"));
+    }
+
+    @Test
+    public void testCreateCodeExchangeProperty() {
+        assertEquals("exchangeProperty(exchange, \"color\")", 
createCode("exchangeProperty.color"));
+    }
+
+    @Test
+    public void testCreateCodeExchangePropertyAs() {
+        assertEquals("exchangePropertyAs(exchange, \"count\", Integer.class)",
+                createCode("exchangePropertyAs(count, Integer)"));
+    }
+
+    @Test
+    public void testCreateCodeExchangePropertyAsIndex() {
+        assertEquals("exchangePropertyAsIndex(exchange, String.class, 
\"list\", \"0\")",
+                createCode("exchangePropertyAsIndex(list, String, 0)"));
+    }
+
+    @Test
+    public void testCreateCodeException() {
+        assertEquals("exception(exchange).getMessage()", 
createCode("exception.message"));
+    }
+
+    @Test
+    public void testCreateCodeExceptionInvalidOgnl() {
+        assertThrows(SimpleParserException.class,
+                () -> createFactory().createCode(context, "exception[bad", 0));
+    }
+
+    @Test
+    public void testCreateCodeExceptionAs() {
+        assertEquals("exceptionAs(exchange, IllegalArgumentException.class)",
+                createCode("exceptionAs(IllegalArgumentException)"));
+    }
+
+    @Test
+    public void testCreateCodeExchange() {
+        assertEquals("exchange.getExchangeId()", 
createCode("exchange.exchangeId"));
+    }
+
+    // --- unrecognized ---
+
+    @Test
+    public void testUnrecognizedFunction() {
+        assertNull(createFactory().createFunction(context, "unknown", 0));
+        assertNull(createFactory().createCode(context, "unknown", 0));
+    }
+}


Reply via email to