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 fef6d46931dd CAMEL-23799: Sort init-block substitution keys by length 
to prevent prefix collision (#24121)
fef6d46931dd is described below

commit fef6d46931dd881cc543e5561e3711ee470d8e65
Author: Adriano Machado <[email protected]>
AuthorDate: Fri Jun 19 15:42:37 2026 -0400

    CAMEL-23799: Sort init-block substitution keys by length to prevent prefix 
collision (#24121)
---
 .../language/simple/SimpleExpressionParser.java    | 13 ++++-
 .../language/simple/SimplePredicateParser.java     |  9 ++-
 .../simple/SimpleInitBlockOverlappingKeysTest.java | 67 ++++++++++++++++++++++
 3 files changed, 84 insertions(+), 5 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
index 018f2a1df742..8a9e0d0d5792 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
@@ -17,6 +17,7 @@
 package org.apache.camel.language.simple;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -88,12 +89,18 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
                     part = part.substring(1);
                 }
                 this.expression = part;
-                // use $$key as local variable in the expression afterwards
-                for (String key : initParser.getInitKeys()) {
+                // use $$key as local variable in the expression afterwards.
+                // Sort by descending length so a longer key (e.g. "$ab") is 
replaced before any
+                // shorter prefix (e.g. "$a"), preventing "$ab" from becoming 
"${variable.a}b".
+                List<String> sortedKeys = new 
ArrayList<>(initParser.getInitKeys());
+                
sortedKeys.sort(Comparator.comparingInt(String::length).reversed());
+                for (String key : sortedKeys) {
                     this.expression = this.expression.replace("$" + key, 
"${variable." + key + "}");
                 }
                 // use $$key() as local function in the expression afterwards
-                for (String key : initParser.getInitFunctions()) {
+                List<String> sortedFunctions = new 
ArrayList<>(initParser.getInitFunctions());
+                
sortedFunctions.sort(Comparator.comparingInt(String::length).reversed());
+                for (String key : sortedFunctions) {
                     // no-arg functions
                     this.expression = this.expression.replace("$" + key + 
"()", "${function(" + key + ")}");
                     // arg functions
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
index d50831ce0529..c52cc491050d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.simple;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Deque;
 import java.util.Iterator;
 import java.util.List;
@@ -99,8 +100,12 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
                         part = part.substring(1);
                     }
                     this.expression = part;
-                    // use $$key as local variable in the expression afterwards
-                    for (String key : initParser.getInitKeys()) {
+                    // use $$key as local variable in the expression 
afterwards.
+                    // Sort by descending length so a longer key (e.g. "$ab") 
is replaced before any
+                    // shorter prefix (e.g. "$a"), preventing "$ab" from 
becoming "${variable.a}b".
+                    List<String> sortedKeys = new 
ArrayList<>(initParser.getInitKeys());
+                    
sortedKeys.sort(Comparator.comparingInt(String::length).reversed());
+                    for (String key : sortedKeys) {
                         this.expression = this.expression.replace("$" + key, 
"${variable." + key + "}");
                     }
                 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockOverlappingKeysTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockOverlappingKeysTest.java
new file mode 100644
index 000000000000..facc266d1645
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockOverlappingKeysTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.apache.camel.LanguageTestSupport;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Regression tests for the init-block key substitution ordering bug (L2). 
When one key is a prefix of another (e.g.
+ * "$n" and "$name"), the shorter key must not be replaced first, or "$name" 
becomes "${variable.n}ame". The fix sorts
+ * keys by descending length before substitution.
+ */
+public class SimpleInitBlockOverlappingKeysTest extends LanguageTestSupport {
+
+    @Override
+    protected String getLanguageName() {
+        return "simple";
+    }
+
+    /**
+     * Two keys where the short key ("n") is a prefix of the long key 
("name"). Without the fix, replacing "$n" first
+     * corrupts "$name" into "${variable.n}ame".
+     */
+    private static final String OVERLAPPING_KEYS = """
+            $init{
+              $n := 'Bob';
+              $name := 'Alice';
+            }init$
+            full=$name,short=$n
+            """;
+
+    /**
+     * Three-level overlap: "v", "val", "value". The longest must be replaced 
first.
+     */
+    private static final String THREE_LEVEL_OVERLAP = """
+            $init{
+              $v := 'one';
+              $val := 'two';
+              $value := 'three';
+            }init$
+            $value,$val,$v
+            """;
+
+    @Test
+    public void testOverlappingKeysTwoLevels() throws Exception {
+        assertExpression(exchange, OVERLAPPING_KEYS, "full=Alice,short=Bob\n");
+    }
+
+    @Test
+    public void testOverlappingKeysThreeLevels() throws Exception {
+        assertExpression(exchange, THREE_LEVEL_OVERLAP, "three,two,one\n");
+    }
+}

Reply via email to