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 5337aee73a02 CAMEL-26886: camel-core - Simple: the other functions
with arguments keep an empty argument, and a comma in a nested function does
not split (#29790)
5337aee73a02 is described below
commit 5337aee73a02404c6ceffab9bdbb22c895b4e45f
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Nov 13 20:23:25 2026 +0530
CAMEL-26886: camel-core - Simple: the other functions with arguments keep
an empty argument, and a comma in a nested function does not split (#29790)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/catalog/docs/simple-functions.adoc | 13 +-
.../modules/languages/pages/simple-functions.adoc | 13 +-
.../language/simple/SimpleFunctionHelper.java | 15 +++
.../functions/CollectionFunctionFactory.java | 19 +--
.../simple/functions/CustomFunctionFactory.java | 10 +-
.../simple/functions/MathFunctionFactory.java | 8 +-
.../simple/functions/MiscFunctionFactory.java | 56 ++++----
.../simple/functions/RandomFunctionFactory.java | 18 +--
.../simple/functions/StringFunctionFactory.java | 28 ++--
.../functions/OtherFunctionArgumentsTest.java | 146 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 13 ++
11 files changed, 268 insertions(+), 71 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
index b19b30092c7f..0a8b7e182582 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
@@ -8,13 +8,14 @@ xref:simple-language.adoc[Back to Simple Language]
The Simple language has many built-in functions which allows access to various
part of Camel and the current `Exchange`, the message payload such as body and
headers, and much more.
NOTE: Some functions take 1 or more arguments enclosed in parentheses, and
arguments separated by comma (ie `${replace('custID','customerId')}`.
-In the arguments of `replace`, `substring`, `substringBefore`,
`substringAfter`, `substringBetween`, `contains`,
-`join`, `split`, `sort`, `setHeader`, `setVariable`, `forEach`, `filter`,
`listAdd`, `listRemove`, `mapAdd` and
-`mapRemove`, a comma inside quotes or inside a nested function such as
`$\{header.name.substring(1,3)}` does not
-separate arguments. An argument left out, as in
+In the arguments of a function, a comma inside quotes or inside a nested
function such as
+`$\{header.name.substring(1,3)}` does not separate arguments. An argument left
out, as in
`${replace(a,,$\{header.name})}`, stays in its place: it means the default
where the function has one (the message
-body for an expression, a comma for a separator, the empty text for the `to`
of `replace`) and is refused otherwise;
-`''` is the empty text.
+body where the function otherwise reads the body, a comma for the separator of
`join` and `split`, the empty text for
+the `to` of `replace`, 0 for the min of `range` and `random`, SHA-256 for the
algorithm of `hash`, a space for the
+separator of `pad`) and is refused otherwise; `''` is the empty text. In the
+values of `sum`, `max`, `min`, `average`, `distinct`, `reverse`, `shuffle`,
`list` and `map` an empty value is no
+value.
=== Inlined Result Type Function
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
index b19b30092c7f..0a8b7e182582 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
@@ -8,13 +8,14 @@ xref:simple-language.adoc[Back to Simple Language]
The Simple language has many built-in functions which allows access to various
part of Camel and the current `Exchange`, the message payload such as body and
headers, and much more.
NOTE: Some functions take 1 or more arguments enclosed in parentheses, and
arguments separated by comma (ie `${replace('custID','customerId')}`.
-In the arguments of `replace`, `substring`, `substringBefore`,
`substringAfter`, `substringBetween`, `contains`,
-`join`, `split`, `sort`, `setHeader`, `setVariable`, `forEach`, `filter`,
`listAdd`, `listRemove`, `mapAdd` and
-`mapRemove`, a comma inside quotes or inside a nested function such as
`$\{header.name.substring(1,3)}` does not
-separate arguments. An argument left out, as in
+In the arguments of a function, a comma inside quotes or inside a nested
function such as
+`$\{header.name.substring(1,3)}` does not separate arguments. An argument left
out, as in
`${replace(a,,$\{header.name})}`, stays in its place: it means the default
where the function has one (the message
-body for an expression, a comma for a separator, the empty text for the `to`
of `replace`) and is refused otherwise;
-`''` is the empty text.
+body where the function otherwise reads the body, a comma for the separator of
`join` and `split`, the empty text for
+the `to` of `replace`, 0 for the min of `range` and `random`, SHA-256 for the
algorithm of `hash`, a space for the
+separator of `pad`) and is refused otherwise; `''` is the empty text. In the
+values of `sum`, `max`, `min`, `average`, `distinct`, `reverse`, `shuffle`,
`list` and `map` an empty value is no
+value.
=== Inlined Result Type Function
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
index 8689f1696911..5e5a4974fd08 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
@@ -158,6 +158,21 @@ public final class SimpleFunctionHelper {
return answer.toArray(new String[0]);
}
+ /**
+ * Splits the arguments of a function as {@link
#splitArgumentsAsWritten(String)} does (an empty argument is kept in
+ * its place, a comma inside a nested function does not split), with the
white space around each argument removed
+ * and its quotes kept, for a function that removes the quotes of some of
its arguments only. An argument left out
+ * (nothing, or only white space, between the commas) is {@code null}.
+ */
+ public static String[] splitArgumentsKeepQuotes(String values) {
+ String[] tokens = splitArgumentsAsWritten(values);
+ for (int i = 0; i < tokens.length; i++) {
+ String token = tokens[i].trim();
+ tokens[i] = token.isEmpty() ? null : token;
+ }
+ return tokens;
+ }
+
/**
* The expression argument of a function as {@link
#splitArguments(String)} gives it, or {@code ${body}} when it is
* left out, such as in {@code ${replace(a,b,)}}.
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
index 3860b2af96ec..d76cbbe393f7 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
@@ -29,6 +29,7 @@ import static
org.apache.camel.language.simple.SimpleFunctionHelper.expressionOr
import static
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.joinArguments;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArguments;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArgumentsKeepQuotes;
/**
* Built-in Simple collection functions: {@code ${setHeader}}, {@code
${setVariable}}, {@code ${range}},
@@ -117,16 +118,16 @@ public final class CollectionFunctionFactory implements
SimpleLanguageFunctionFa
throw new SimpleParserException(
"Valid syntax: ${range(min,max)} or ${range(max)} was:
" + function, index);
}
- if (values.contains(",")) {
- String[] tokens = values.split(",", 3);
- if (tokens.length > 2) {
- throw new SimpleParserException(
- "Valid syntax: ${range(min,max)} or ${range(max)}
was: " + function, index);
- }
- return
CollectionExpressionBuilder.rangeExpression(tokens[0].trim(), tokens[1].trim());
- } else {
- return CollectionExpressionBuilder.rangeExpression("0",
values.trim());
+ // a comma inside a nested function is part of the argument; a min
left out is 0
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length > 2 || tokens[tokens.length - 1] == null) {
+ throw new SimpleParserException(
+ "Valid syntax: ${range(min,max)} or ${range(max)} was:
" + function, index);
+ }
+ if (tokens.length == 2) {
+ return CollectionExpressionBuilder.rangeExpression(tokens[0]
!= null ? tokens[0] : "0", tokens[1]);
}
+ return CollectionExpressionBuilder.rangeExpression("0", tokens[0]);
}
remainder = ifStartsWithReturnRemainder("distinct(", function);
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
index ff05c5d05a6f..676f20a6e0d6 100644
---
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
@@ -23,9 +23,9 @@ 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;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArgumentsKeepQuotes;
/**
* Built-in Simple function for user-defined custom functions: {@code
${function(name)}} and
@@ -47,14 +47,16 @@ public final class CustomFunctionFactory implements
SimpleLanguageFunctionFactor
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) {
+ // a comma inside quotes or a nested function is part of the expression
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length > 2 || tokens[0] == null) {
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) {
+ // an expression left out is the message body
+ if (tokens.length == 2 && tokens[1] != null) {
param = tokens[1];
param = StringHelper.removeLeadingAndEndingQuotes(param.trim());
}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
index ff4c848cfa31..c7c901528b1d 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
@@ -16,15 +16,17 @@
*/
package org.apache.camel.language.simple.functions;
+import java.util.Arrays;
+
import org.apache.camel.CamelContext;
import org.apache.camel.Expression;
import org.apache.camel.language.simple.MathExpressionBuilder;
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;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArguments;
/**
* Built-in Simple math functions: {@code ${abs}}, {@code ${floor}}, {@code
${ceil}}, {@code ${sum}}, {@code ${max}},
@@ -77,7 +79,9 @@ public final class MathFunctionFactory implements
SimpleLanguageFunctionFactory
private static String[] parseVariadicTokens(String remainder) {
String values = StringHelper.beforeLast(remainder, ")");
if (ObjectHelper.isNotEmpty(values)) {
- return StringQuoteHelper.splitSafeQuote(values, ',', true, false);
+ // a comma inside quotes or a nested function is part of the
value; as before, an empty value is no value
+ return Arrays.stream(splitArguments(values)).filter(v -> v != null
&& !v.isBlank())
+ .toArray(String[]::new);
}
// no values, such as ${sum()}, then use the message body
return new String[] { "${body}" };
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
index 2e26d45bd1fe..48734aa54206 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
@@ -25,9 +25,9 @@ import org.apache.camel.spi.SimpleLanguageFunctionFactory;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.OgnlHelper;
import org.apache.camel.util.StringHelper;
-import org.apache.camel.util.StringQuoteHelper;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArgumentsKeepQuotes;
/**
* Built-in Simple miscellaneous functions: {@code ${isEmpty}}, {@code
${isAlpha}}, {@code ${isAlphaNumeric}},
@@ -110,14 +110,15 @@ public final class MiscFunctionFactory implements
SimpleLanguageFunctionFactory
throw new SimpleParserException(
"Valid syntax: ${throwException(msg)} or
${throwException(msg,type)} was: " + function, index);
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
- if (tokens.length > 2) {
+ // a comma inside a quoted message or a nested function is part of
the message
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length > 2 || tokens[0] == null) {
throw new SimpleParserException(
"Valid syntax: ${throwException(msg)} or
${throwException(msg,type)} was: " + function, index);
}
- // a comma inside a quoted message is part of the message
msg = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
- if (tokens.length == 2) {
+ if (tokens.length == 2 && tokens[1] != null) {
+ // a type left out is the default type
type = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
}
return MiscExpressionBuilder.throwExceptionExpression(msg, type);
@@ -129,8 +130,8 @@ public final class MiscFunctionFactory implements
SimpleLanguageFunctionFactory
if (values == null || ObjectHelper.isEmpty(values)) {
throw new SimpleParserException("Valid syntax:
${assert(exp,msg)} was: " + function, index);
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
- if (tokens.length != 2) {
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length != 2 || tokens[0] == null || tokens[1] == null) {
throw new SimpleParserException("Valid syntax:
${assert(exp,msg)} was: " + function, index);
}
return MiscExpressionBuilder.assertExpression(tokens[0],
StringHelper.removeLeadingAndEndingQuotes(tokens[1]));
@@ -146,16 +147,19 @@ public final class MiscFunctionFactory implements
SimpleLanguageFunctionFactory
throw new SimpleParserException(
"Valid syntax: ${convertTo(type)} or
${convertTo(exp,type)} was: " + function, index);
}
- if (values.contains(",")) {
- String[] tokens = StringQuoteHelper.splitSafeQuote(values,
',', true, true);
- if (tokens.length > 2) {
- throw new SimpleParserException(
- "Valid syntax: ${convertTo(type)} or
${convertTo(exp,type)} was: " + function, index);
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length > 2 || tokens[tokens.length - 1] == null) {
+ throw new SimpleParserException(
+ "Valid syntax: ${convertTo(type)} or
${convertTo(exp,type)} was: " + function, index);
+ }
+ if (tokens.length == 2) {
+ // an expression left out is the message body
+ if (tokens[0] != null) {
+ exp = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
}
- exp = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
type = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
} else {
- type =
StringHelper.removeLeadingAndEndingQuotes(values.trim());
+ type = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
}
remainder = remainder.substring(end + 1);
if (ObjectHelper.isNotEmpty(remainder)) {
@@ -196,19 +200,21 @@ public final class MiscFunctionFactory implements
SimpleLanguageFunctionFactory
remainder = ifStartsWithReturnRemainder("hash(", function);
if (remainder != null) {
String values = StringHelper.beforeLast(remainder, ")");
- if (values == null || ObjectHelper.isEmpty(values)) {
+ if (values == null) {
throw new SimpleParserException(
"Valid syntax: ${hash(value,algorithm)} or
${hash(value)} was: " + function, index);
}
- // a comma inside quotes is part of the value
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
+ // ${hash()}: the message body with SHA-256, as documented; a
comma inside quotes or a nested function is
+ // part of the value
+ String[] tokens = ObjectHelper.isEmpty(values) ? new String[] {
null } : splitArgumentsKeepQuotes(values);
if (tokens.length > 2) {
throw new SimpleParserException(
"Valid syntax: ${hash(value,algorithm)} or
${hash(value)} was: " + function, index);
}
- String value =
StringHelper.removeLeadingAndEndingQuotes(tokens[0].trim());
- if (tokens.length == 2) {
- return MiscExpressionBuilder.hashExpression(value,
StringHelper.removeLeadingAndEndingQuotes(tokens[1].trim()));
+ // a value left out is the message body, an algorithm left out is
SHA-256
+ String value = tokens[0] != null ?
StringHelper.removeLeadingAndEndingQuotes(tokens[0]) : "${body}";
+ if (tokens.length == 2 && tokens[1] != null) {
+ return MiscExpressionBuilder.hashExpression(value,
StringHelper.removeLeadingAndEndingQuotes(tokens[1]));
} else {
return MiscExpressionBuilder.hashExpression(value, "SHA-256");
}
@@ -239,17 +245,17 @@ public final class MiscFunctionFactory implements
SimpleLanguageFunctionFactory
throw new SimpleParserException(
"Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function, index);
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
- if (tokens.length != 3) {
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length != 3 || tokens[0] == null || tokens[1] == null
|| tokens[2] == null) {
throw new SimpleParserException(
"Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function, index);
}
// the predicate keeps its quotes (CAMEL-22204); a quoted value
loses its quotes, as with the ternary
// operator, but unlike the ternary the text inside the quotes is
still evaluated as Simple
// ('Hello ${body}' gives Hello World)
- return MiscExpressionBuilder.iifExpression(tokens[0].trim(),
-
StringHelper.removeLeadingAndEndingQuotes(tokens[1].trim()),
-
StringHelper.removeLeadingAndEndingQuotes(tokens[2].trim()));
+ return MiscExpressionBuilder.iifExpression(tokens[0],
+ StringHelper.removeLeadingAndEndingQuotes(tokens[1]),
+ StringHelper.removeLeadingAndEndingQuotes(tokens[2]));
}
remainder = ifStartsWithReturnRemainder("load(", function);
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
index 1361729fa3d5..4f65771065b9 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
@@ -25,6 +25,7 @@ import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArgumentsKeepQuotes;
/**
* Built-in Simple function: {@code ${random(max)}} / {@code
${random(min,max)}}.
@@ -42,14 +43,15 @@ public final class RandomFunctionFactory implements
SimpleLanguageFunctionFactor
throw new SimpleParserException(
"Valid syntax: ${random(min,max)} or ${random(max)} was: "
+ function, index);
}
- if (values.contains(",")) {
- String[] tokens = values.split(",", 3);
- if (tokens.length > 2) {
- throw new SimpleParserException(
- "Valid syntax: ${random(min,max)} or ${random(max)}
was: " + function, index);
- }
- return MiscExpressionBuilder.randomExpression(tokens[0].trim(),
tokens[1].trim());
+ // a comma inside a nested function is part of the argument; a min
left out is 0
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length > 2 || tokens[tokens.length - 1] == null) {
+ throw new SimpleParserException(
+ "Valid syntax: ${random(min,max)} or ${random(max)} was: "
+ function, index);
+ }
+ if (tokens.length == 2) {
+ return MiscExpressionBuilder.randomExpression(tokens[0] != null ?
tokens[0] : "0", tokens[1]);
}
- return MiscExpressionBuilder.randomExpression("0", values.trim());
+ return MiscExpressionBuilder.randomExpression("0", tokens[0]);
}
}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
index 6d1daa522907..8e37ccc6d96c 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
@@ -25,11 +25,11 @@ import org.apache.camel.support.builder.ExpressionBuilder;
import org.apache.camel.util.EscapeHelper;
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.expressionOrBody;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArguments;
+import static
org.apache.camel.language.simple.SimpleFunctionHelper.splitArgumentsKeepQuotes;
/**
* Built-in Simple string functions: {@code ${replace}}, {@code ${substring}},
{@code ${substringBefore}},
@@ -259,17 +259,18 @@ public final class StringFunctionFactory implements
SimpleLanguageFunctionFactor
String values = StringHelper.beforeLast(remainder, ")");
if (values == null || ObjectHelper.isEmpty(values)) {
throw new SimpleParserException(
- "Valid syntax: ${pad(len)} or ${pad(exp,len)} or
${pad(exp,len,separator)} was: " + function,
- index);
+ "Valid syntax: ${pad(exp,len)} or
${pad(exp,len,separator)} was: " + function, index);
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
- if (tokens.length < 2 || tokens.length > 3) {
+ // a comma inside quotes or a nested function is part of the
argument
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ if (tokens.length < 2 || tokens.length > 3 || tokens[0] == null ||
tokens[1] == null) {
throw new SimpleParserException(
"Valid syntax: ${pad(exp,len)} or
${pad(exp,len,separator)} was: " + function, index);
}
exp = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
len = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
- if (tokens.length == 3) {
+ // a separator left out is a space
+ if (tokens.length == 3 && tokens[2] != null) {
separator =
StringHelper.removeLeadingAndEndingQuotes(tokens[2]);
}
return StringExpressionBuilder.padExpression(exp, len, separator);
@@ -287,18 +288,23 @@ public final class StringFunctionFactory implements
SimpleLanguageFunctionFactor
+ function,
index);
}
- // a comma inside quotes is part of the value, such as
${concat('Hello, ')}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
true, true);
- if (tokens.length > 3) {
+ // a comma inside quotes or a nested function is part of the
value, such as ${concat('Hello, ')}
+ String[] tokens = splitArgumentsKeepQuotes(values);
+ // the expression that must be given: the only one, or the second
one when there are more
+ int expIdx = tokens.length == 1 ? 0 : 1;
+ if (tokens.length > 3 || tokens[expIdx] == null) {
throw new SimpleParserException(
"Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or
${concat(exp,exp,separator)} was: "
+ function,
index);
}
if (tokens.length >= 2) {
- exp1 = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+ // a first expression left out is the message body, a
separator left out is none
+ if (tokens[0] != null) {
+ exp1 =
StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+ }
exp2 = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
- if (tokens.length == 3) {
+ if (tokens.length == 3 && tokens[2] != null) {
separator =
StringHelper.removeLeadingAndEndingQuotes(tokens[2]);
}
} else {
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/OtherFunctionArgumentsTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/OtherFunctionArgumentsTest.java
new file mode 100644
index 000000000000..d4364fa7fbc3
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/OtherFunctionArgumentsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 java.util.List;
+
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The arguments of the functions that did not split their arguments with the
rules of CAMEL-26465: an argument left out
+ * keeps its place, and a comma inside a nested function does not split an
argument.
+ */
+public class OtherFunctionArgumentsTest extends
AbstractSimpleFunctionFactoryTestSupport {
+
+ @BeforeEach
+ public void setUpExchange() {
+ exchange.getIn().setBody("abc");
+ exchange.getIn().setHeader("s", "abcdef");
+ exchange.getIn().setHeader("n", "1234");
+ PluginHelper.getSimpleFunctionRegistry(context).addFunction(new
SimpleFunction() {
+ @Override
+ public String getName() {
+ return "shout";
+ }
+
+ @Override
+ public Object apply(Exchange exchange, Object input) {
+ return input + "!";
+ }
+ });
+ }
+
+ @Override
+ protected SimpleLanguageFunctionFactory createFactory() {
+ return new MiscFunctionFactory();
+ }
+
+ @Test
+ public void testCommaInsideANestedFunction() {
+ assertEquals("bc-de",
evaluate("concat(${header.s.substring(1,3)},${header.s.substring(3,5)},-)",
String.class));
+ assertEquals("bc ", evaluate("pad(${header.s.substring(1,3)},5)",
String.class));
+ assertEquals(23, evaluate("convertTo(${header.n.substring(1,3)},int)",
Integer.class));
+ assertEquals("bc", evaluate("iif(${header.s.substring(0,1)} ==
'a',${header.s.substring(1,3)},no)", String.class));
+ assertEquals("bc!",
evaluate("function(shout,${header.s.substring(1,3)})", String.class));
+ assertEquals(evaluate("hash(bc)", String.class),
evaluate("hash(${header.s.substring(1,3)})", String.class));
+ assertEquals(35L, evaluate("sum(${header.n.substring(1,3)},12)",
Long.class));
+ assertEquals(23L, evaluate("max(${header.n.substring(1,3)},12)",
Long.class));
+ assertEquals("[23, 24]",
evaluate("range(${header.n.substring(1,3)},25)", List.class).toString());
+ int random = evaluate("random(${header.n.substring(1,3)},24)",
Integer.class);
+ assertEquals(23, random);
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () ->
evaluate("throwException(${header.s.substring(1,3)},java.lang.IllegalStateException)"));
+ assertTrue(e.getMessage().contains("bc"), e.getMessage());
+ }
+
+ @Test
+ public void testArgumentLeftOutWithADefault() {
+ // the first expression of concat, and the expression of convertTo,
function and hash, is the message body
+ assertEquals("abcx", evaluate("concat(,x)", String.class));
+ assertEquals("abc-x", evaluate("concat(,x,-)", String.class));
+ exchange.getIn().setBody("42");
+ assertEquals(42, evaluate("convertTo(,int)", Integer.class));
+ assertEquals("42!", evaluate("function(shout,)", String.class));
+ assertEquals(evaluate("hash(${body})", String.class),
evaluate("hash(,SHA-256)", String.class));
+ assertEquals(evaluate("hash(${body})", String.class),
evaluate("hash()", String.class));
+ // the algorithm of hash is SHA-256, the separator of pad a space, of
concat none
+ assertEquals(evaluate("hash(${body})", String.class),
evaluate("hash(${body},)", String.class));
+ assertEquals("42 ", evaluate("pad(${body},5,)", String.class));
+ assertEquals("42x", evaluate("concat(${body},x,)", String.class));
+ // the min of range and random is 0
+ assertEquals("[0, 1]", evaluate("range(,2)", List.class).toString());
+ assertEquals(0, evaluate("random(,1)", Integer.class));
+ // the type of throwException is IllegalArgumentException
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () ->
evaluate("throwException(Oops,)"));
+ assertEquals("Oops", e.getMessage());
+ }
+
+ @Test
+ public void testArgumentLeftOutWithoutADefaultIsRefused() {
+ // before, the next argument took its place, or the argument was
dropped
+ String[][] refused = {
+ { "misc", "throwException(,java.lang.IllegalStateException)" },
+ { "misc", "assert(,Must be Hello)" },
+ { "misc", "assert(${body} == 'abc',)" },
+ { "misc", "convertTo(${body},)" },
+ { "misc", "iif(${body} == 'abc',,no)" },
+ { "misc", "iif(${body} == 'abc',yes,)" },
+ { "misc", "iif(,yes,no)" },
+ { "string", "pad(,5)" },
+ { "string", "pad(${body},)" },
+ { "string", "concat(${body},)" },
+ { "string", "concat(${body},,-)" },
+ { "custom", "function(,${body})" },
+ { "collection", "range(1,)" },
+ { "random", "random(1,)" } };
+ for (String[] r : refused) {
+ SimpleLanguageFunctionFactory factory = switch (r[0]) {
+ case "misc" -> new MiscFunctionFactory();
+ case "string" -> new StringFunctionFactory();
+ case "custom" -> new CustomFunctionFactory();
+ case "collection" -> new CollectionFunctionFactory();
+ default -> new RandomFunctionFactory();
+ };
+ assertThrows(SimpleParserException.class, () ->
factory.createFunction(context, r[1], 0), r[1]);
+ }
+ }
+
+ @Test
+ public void testEmptyValueOfAMathFunctionIsNoValue() {
+ // the values of sum, max, min and average are a list: an empty value
is no value, as before
+ assertEquals(3L, evaluate("sum(1,,2)", Long.class));
+ assertEquals(2L, evaluate("max(1,,2)", Long.class));
+ }
+
+ @Test
+ public void testQuotesAndSpacesAsBefore() {
+ assertEquals("Hello, abc", evaluate("concat('Hello, ',${body})",
String.class));
+ assertEquals("@@abc", evaluate("pad(${body}, -5, '@')", String.class));
+ assertEquals("yes", evaluate("iif(${body} == 'abc', 'yes' , 'no')",
String.class));
+ assertEquals(42, evaluate("convertTo( '42' , int )", Integer.class));
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index eb1094feafd4..1f74fee69eee 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -841,6 +841,19 @@ longer separates two arguments; a parenthesis outside a
nested function is text,
after an unquoted `(` or `{` no longer separates two values: quote a value
with a lone parenthesis. A lone quote at the
end of an argument is now kept: `+${replace(b,x')}+` replaces with `x'`.
+=== camel-core - Simple: the other functions with arguments keep an empty
argument too
+
+The rules of the entry above now also apply to `concat`, `pad`, `convertTo`,
`iif`, `hash`, `assert`, `throwException`,
+`function`, `range`, `random`, `sum`, `max`, `min` and `average`: a comma
inside a nested function, such as
+`+${header.s.substring(1,3)}+`, no longer separates two arguments, and an
argument left out stays in its place. Where
+the function has a default for it, that default is used: `+${concat(,x,-)}+`
gives the body, `-` and `x` (it gave `x-`),
+`+${hash(,SHA3-256)}+` hashes the body with SHA3-256 (it hashed the text
`SHA3-256`). Where it has none it is refused,
+such as `+${throwException(,java.io.IOException)}+`, `+${concat(x,)}+` or
`+${function(,f)}+`, and a stray comma after
+the last argument that was ignored is now an error, such as `+${iif(p,a,b,)}+`
or `+${hash(v,SHA-1,)}+`: remove the
+comma (which gives the result of before for each of these examples). A lone
quote at the end of an argument is now kept,
+as in the entry above. An empty value of `sum`, `max`, `min` or `average` is
still no value. `+${hash()}+` now hashes
+the body with SHA-256, as documented (it was refused).
+
=== camel-core - a route called from a doTry uses the error handler of its EIPs
A route that a `doTry` calls from its steps, or from its `doCatch` or
`doFinally` blocks (with `direct`, `seda`,