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 3c2fbfe07beb CAMEL-26420: camel-core - Simple: the white space around 
the prefix and the expression of join is not part of them (#29044)
3c2fbfe07beb is described below

commit 3c2fbfe07beb275fb643c0df485448d36f94d42d
Author: allthingssecurity <[email protected]>
AuthorDate: Tue Nov 3 13:26:06 2026 +0530

    CAMEL-26420: camel-core - Simple: the white space around the prefix and the 
expression of join is not part of them (#29044)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/catalog/docs/simple-functions.adoc       |  4 +-
 .../modules/languages/pages/simple-functions.adoc  |  4 +-
 .../language/simple/SimpleFunctionHelper.java      | 22 ++++++----
 .../simple/functions/JoinFunctionFactory.java      | 25 +++++++-----
 .../simple/functions/JoinFunctionFactoryTest.java  | 47 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  9 +++++
 6 files changed, 92 insertions(+), 19 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 bcf1371e8f6b..af676991a731 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
@@ -563,7 +563,9 @@ In the example we had to add a space after `"Hello "`, but 
you can also use the
 The `join` function is a more complex function to join together a set of 
values, separated by comma (by default) as a single combined String.
 For example if the message body contains a list of [A, B, C] then `${join()}` 
would return `A,B,C`.
 To use semicolon as separator you would use `${join(;)}`. The `prefix` 
argument can be used to prefix each value, so for example
-`${join(&,id=)}` would return `id=A&id=B&id=C`.
+`${join(&,id=)}` would return `id=A&id=B&id=C`. The white space around the 
prefix and the expression is not part of
+them, so `${join(&, id=)}` returns the same. An unquoted separator is kept as 
written, so `${join(; )}` returns
+`A; B; C`; a quoted one is the text inside its quotes.
 
 The `escape` function escapes special characters so a value can be safely 
embedded in another format,
 instead of chaining multiple `replace` calls or using a custom bean. The first 
argument is the kind of escaping:
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 bcf1371e8f6b..af676991a731 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
@@ -563,7 +563,9 @@ In the example we had to add a space after `"Hello "`, but 
you can also use the
 The `join` function is a more complex function to join together a set of 
values, separated by comma (by default) as a single combined String.
 For example if the message body contains a list of [A, B, C] then `${join()}` 
would return `A,B,C`.
 To use semicolon as separator you would use `${join(;)}`. The `prefix` 
argument can be used to prefix each value, so for example
-`${join(&,id=)}` would return `id=A&id=B&id=C`.
+`${join(&,id=)}` would return `id=A&id=B&id=C`. The white space around the 
prefix and the expression is not part of
+them, so `${join(&, id=)}` returns the same. An unquoted separator is kept as 
written, so `${join(; )}` returns
+`A; B; C`; a quoted one is the text inside its quotes.
 
 The `escape` function escapes special characters so a value can be safely 
embedded in another format,
 instead of chaining multiple `replace` calls or using a custom bean. The first 
argument is the kind of escaping:
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 e56fdb14b3f7..a93e0835beb3 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
@@ -109,14 +109,22 @@ public final class SimpleFunctionHelper {
     public static String[] splitArguments(String values) {
         String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false, 
true);
         for (int i = 0; i < tokens.length; i++) {
-            String token = tokens[i];
-            String trimmed = token.trim();
-            if (!trimmed.isEmpty()) {
-                // a whitespace only argument such as a new line is kept as-is
-                token = trimmed;
-            }
-            tokens[i] = StringHelper.removeLeadingAndEndingQuotes(token);
+            tokens[i] = trimArgument(tokens[i]);
         }
         return tokens;
     }
+
+    /**
+     * An argument as {@link #splitArguments(String)} gives it, from the 
argument as written (split with
+     * {@code StringQuoteHelper.splitSafeQuote(values, ',', false, true)}): 
without the white space and the quotes
+     * around it, unless it is only white space.
+     */
+    public static String trimArgument(String token) {
+        String trimmed = token.trim();
+        if (!trimmed.isEmpty()) {
+            // a whitespace only argument such as a new line is kept as-is
+            token = trimmed;
+        }
+        return StringHelper.removeLeadingAndEndingQuotes(token);
+    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
index f9ff0803eaf0..7a9d34337d8d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
@@ -26,10 +26,15 @@ 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.trimArgument;
 
 /**
  * Built-in Simple function: {@code ${join()}} / {@code ${join(separator)}} / 
{@code ${join(separator,prefix)}} /
  * {@code ${join(separator,prefix,expression)}}.
+ * <p>
+ * The white space around the prefix and the expression is not part of them, 
as for the arguments of the other
+ * functions. An unquoted separator is kept as written, as it can be white 
space on purpose: {@code ${join(; )}} joins
+ * with {@code "; "}; a quoted one is the text inside its quotes.
  */
 public final class JoinFunctionFactory implements 
SimpleLanguageFunctionFactory {
 
@@ -44,20 +49,20 @@ public final class JoinFunctionFactory implements 
SimpleLanguageFunctionFactory
         String prefix = null;
         String exp = "${body}";
         if (ObjectHelper.isNotEmpty(values)) {
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            // the arguments as written (with their quotes), split at the 
commas outside quotes as splitArguments does,
+            // so the separator can be kept as written
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false, true);
             if (tokens.length > 3) {
                 throw new SimpleParserException(
                         "Valid syntax: ${join(separator,prefix,expression)} 
was: " + function, index);
             }
-            if (tokens.length == 3) {
-                separator = tokens[0];
-                prefix = tokens[1];
-                exp = tokens[2];
-            } else if (tokens.length == 2) {
-                separator = tokens[0];
-                prefix = tokens[1];
-            } else {
-                separator = tokens[0];
+            separator = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+            if (tokens.length > 1) {
+                // the prefix and the expression without the white space and 
the quotes around them
+                prefix = trimArgument(tokens[1]);
+                if (tokens.length == 3) {
+                    exp = trimArgument(tokens[2]);
+                }
             }
         }
         return CollectionExpressionBuilder.joinExpression(exp, separator, 
prefix);
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
index b7e090d6e79a..71a46a75e97c 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
@@ -56,4 +56,51 @@ public class JoinFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSu
 
         assertEquals("id=A&id=B&id=C", 
evaluate("join('&','id=','${header.id}')", String.class));
     }
+
+    @Test
+    public void testJoinSpaceAroundPrefixAndExpression() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        exchange.getIn().setBody(data);
+        exchange.getIn().setHeader("id", List.of("X", "Y"));
+
+        // the white space around the prefix and the expression is not part of 
them
+        assertEquals("id=A&id=B&id=C", evaluate("join(&, id=)", String.class));
+        assertEquals("id=A&id=B&id=C", evaluate("join(&, id= )", 
String.class));
+        assertEquals("id=X&id=Y", evaluate("join(&, id=, ${header.id})", 
String.class));
+        assertEquals("id=X&id=Y", evaluate("join('&', 'id=', '${header.id}')", 
String.class));
+        // a space inside quotes is kept
+        assertEquals(" id=A& id=B& id=C", evaluate("join(&,' id=')", 
String.class));
+    }
+
+    @Test
+    public void testJoinSeparatorKeptAsWritten() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        exchange.getIn().setBody(data);
+
+        // the separator can be white space on purpose, so it is kept as 
written
+        assertEquals("A; B; C", evaluate("join(; )", String.class));
+        assertEquals("id=A; id=B; id=C", evaluate("join(; ,id=)", 
String.class));
+        assertEquals("id=A; id=B; id=C", evaluate("join('; ', id=)", 
String.class));
+    }
+
+    @Test
+    public void testJoinQuotesInsideAnArgumentAreKept() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        exchange.getIn().setBody(data);
+        exchange.getIn().setHeader("x", "Hello,World");
+
+        // only the quotes around a whole argument are removed, as for the 
other functions
+        assertEquals("a'b'cA&a'b'cB", evaluate("join(&,a'b'c)", String.class));
+        assertEquals("id=Hello&id=World", 
evaluate("join(&,id=,${header.x.split(',')})", String.class));
+        // a quoted separator is the text inside its quotes
+        assertEquals("id=A&id=B", evaluate("join('&' , id=)", String.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 c843baa34182..66d2601aecdf 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
@@ -355,6 +355,15 @@ space next to the comma, such as `+${replace(a,b )}+` for 
the text `++b ++`, quo
 Only the quotes around a whole argument are removed: a quote inside an 
argument, as in `+a'b'c+`, is now kept (it was
 removed).
 
+The prefix and the expression of the `join` function no longer include the 
white space around them either:
+for the body `[A, B, C]`, `+${join(&, id=)}+` gives `id=A&id=B&id=C` (it gave 
`++ id=A& id=B& id=C++`). An unquoted
+separator is kept as written, as it can be white space on purpose: `+${join(; 
)}+` still joins with `++; ++`; a quoted
+separator is the text inside its quotes (`+${join('&' ,id=)}+` joins with `&`, 
it joined with `++& ++`; put the space
+inside the quotes to keep it: `+${join('& ',id=)}+`). A prefix with a space at 
its start or end is quoted instead:
+`+${join(&,' id=')}+`. As for the other functions, only the quotes around a 
whole argument of
+`join` are removed: a quote inside an argument, as in `+${join(&,a'b'c)}+`, is 
now kept (it was removed), so an
+expression such as `+${join(&,id=,${header.x.split(',')})}+` now works.
+
 === 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`,

Reply via email to