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));
+ }
+}