You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2022/10/12 13:07:03 UTC

[camel] branch main updated: CAMEL-18576: camel-base - Escape a placeholder (#8521)

This is an automated email from the ASF dual-hosted git repository.

nfilotto 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 2024ab9acc4 CAMEL-18576: camel-base - Escape a placeholder (#8521)
2024ab9acc4 is described below

commit 2024ab9acc42d25c61b9a5377a2bb07cc828fbf9
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Wed Oct 12 15:06:56 2022 +0200

    CAMEL-18576: camel-base - Escape a placeholder (#8521)
    
    ## Motivation
    
    The elastic search queries can contain double curly braces which are seen as a property placeholder by Camel, we need a way to escape it.
    
    ## Modifications:
    
    * Propose to use the backslash character to escape double curly braces
---
 .../src/main/docs/properties-component.adoc        |  7 ++++
 .../properties/DefaultPropertiesParser.java        | 30 ++++++++++++---
 .../component/properties/PropertiesComponent.java  | 38 +++++++++++++++++++
 ...st.java => PropertiesComponentEscapedTest.java} | 43 ++++++++++++----------
 .../PropertiesComponentLoadPropertiesTest.java     |  6 +--
 .../component/properties/myproperties.properties   |  1 +
 6 files changed, 97 insertions(+), 28 deletions(-)

diff --git a/core/camel-base/src/main/docs/properties-component.adoc b/core/camel-base/src/main/docs/properties-component.adoc
index 2eceaa06d6f..186be6a4096 100644
--- a/core/camel-base/src/main/docs/properties-component.adoc
+++ b/core/camel-base/src/main/docs/properties-component.adoc
@@ -119,6 +119,13 @@ For fine grained configuration of the location, then this can be done as follows
 </camelContext>
 ----
 
+=== Escape a placeholder
+
+The component allows to refer to the value of a property thanks to placeholders of type `{{property-name}}` but sometimes it can be problematic if the double curly braces are used by a third party library.
+
+To work around that it is possible to escape the double curly braces with a backslash character like for example `\{{ property-name \}}`. This way, it won't be interpreted as a placeholder to resolve and will be resolved as `{{property-name}}`.
+
+If for some reason, the backslash character before the double curly braces must not be interpreted as an escape character, it is possible to add another backslash in front of it to escape it, it will then be seen as a backslash.
 
 == Options
 
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
index afb5b5c73f1..7cb0d895576 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
@@ -172,13 +172,18 @@ public class DefaultPropertiesParser implements PropertiesParser {
                 Set<String> newReplaced = new HashSet<>(replacedPropertyKeys);
                 newReplaced.add(property.getKey());
 
-                String before = answer.substring(0, property.getBeginIndex());
+                int beginIndex = property.getBeginIndex();
+                if (beginIndex > 0 && answer.charAt(beginIndex - 1) == '\\') {
+                    // The escape character has been escaped, so we need to restore it
+                    beginIndex--;
+                }
+                String before = answer.substring(0, beginIndex);
                 String after = answer.substring(property.getEndIndex());
                 String parsed = doParseNested(property.getValue(), newReplaced);
                 if (parsed != null) {
                     answer = before + parsed + after;
                 } else {
-                    if (property.getBeginIndex() == 0 && input.length() == property.getEndIndex()) {
+                    if (beginIndex == 0 && input.length() == property.getEndIndex()) {
                         // its only a single placeholder which is parsed as null
                         answer = null;
                         break;
@@ -229,7 +234,7 @@ public class DefaultPropertiesParser implements PropertiesParser {
             int index = -1;
             do {
                 index = input.indexOf(SUFFIX_TOKEN, index + 1);
-            } while (index != -1 && isQuoted(input, index, SUFFIX_TOKEN));
+            } while (index != -1 && (isQuoted(input, index, SUFFIX_TOKEN) || isEscaped(input, index - 1)));
             return index;
         }
 
@@ -246,12 +251,12 @@ public class DefaultPropertiesParser implements PropertiesParser {
             int index = suffixIndex;
             do {
                 index = input.lastIndexOf(PREFIX_TOKEN, index - 1);
-            } while (index != -1 && isQuoted(input, index, PREFIX_TOKEN));
+            } while (index != -1 && (isQuoted(input, index, PREFIX_TOKEN) || isEscaped(input, index - 1)));
             return index;
         }
 
         /**
-         * Indicates whether or not the token at the given index is surrounded by single or double quotes
+         * Indicates whether the token at the given index is surrounded by single or double quotes
          *
          * @param  input Input string
          * @param  index Index of the token
@@ -269,6 +274,21 @@ public class DefaultPropertiesParser implements PropertiesParser {
             return false;
         }
 
+        /**
+         * Indicates whether the escape character is at the given index.
+         *
+         * @param  input Input string
+         * @param  index Index where the escape character is checked.
+         * @return       {@code true} if the escape character is at the given index, and it is not itself escaped,
+         *               {@code false} otherwise.
+         */
+        private boolean isEscaped(String input, int index) {
+            if (index >= 0) {
+                return input.charAt(index) == '\\' && (index == 0 || input.charAt(index - 1) != '\\');
+            }
+            return false;
+        }
+
         /**
          * Gets the value of the property with given key
          *
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
index 02888ccd9ed..47737b59e11 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
@@ -320,6 +320,10 @@ public class PropertiesComponent extends ServiceSupport
                 answer = "true";
             }
         }
+        if (answer != null) {
+            // Remove the escape characters if any
+            answer = unescape(answer);
+        }
         LOG.trace("Parsed uri {} -> {}", uri, answer);
         return answer;
     }
@@ -815,4 +819,38 @@ public class PropertiesComponent extends ServiceSupport
         return answer;
     }
 
+    /**
+     * Replaces all the double curly braces that have been escaped by double curly braces.
+     *
+     * @param  input the content to unescape
+     * @return       the provided content with all the escaped double curly braces restored.
+     */
+    private static String unescape(String input) {
+        int index = input.indexOf('\\');
+        if (index == -1) {
+            return input;
+        }
+        int length = input.length();
+        StringBuilder result = new StringBuilder(length);
+        int start = 0;
+        do {
+            result.append(input, start, index);
+            start = index + 1;
+            if (index + 2 < length) {
+                char next = input.charAt(index + 1);
+                char afterNext = input.charAt(index + 2);
+                if (next == '{' && afterNext == '{' || next == '}' && afterNext == '}') {
+                    // Escaped double curly braces detected, so let's keep the escape character
+                    continue;
+                }
+                result.append('\\');
+            } else {
+                break;
+            }
+        } while ((index = input.indexOf('\\', start)) != -1);
+        if (start < length) {
+            result.append(input, start, length);
+        }
+        return result.toString();
+    }
 }
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java
similarity index 51%
copy from core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java
copy to core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java
index 211bec4234f..f77ac5681d7 100644
--- a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java
@@ -16,35 +16,38 @@
  */
 package org.apache.camel.component.properties;
 
-import java.util.Properties;
-
 import org.apache.camel.CamelContext;
 import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-public class PropertiesComponentLoadPropertiesTest extends ContextTestSupport {
-
-    @Override
-    public boolean isUseRouteBuilder() {
-        return false;
-    }
+class PropertiesComponentEscapedTest extends ContextTestSupport {
 
     @Test
-    public void testLoadProperties() throws Exception {
-        context.start();
+    void testEscaped() throws Exception {
+        getMockEndpoint("mock:result")
+                .expectedBodiesReceived("{{before}}mock:{{cool.result}}{\"query\":{\"match_all\":{}}}{{after}}");
+        getMockEndpoint("mock:result").expectedHeaderReceived("foo",
+                "Hello mock:{{cool.result}}{\"query\":{\"match_all\":{}}} and {{before}}Cheese{{after}}/\\Cheese how are you?");
 
-        org.apache.camel.spi.PropertiesComponent pc = context.getPropertiesComponent();
-        Properties prop = pc.loadProperties();
+        template.sendBody("direct:start", "Hello World");
 
-        assertNotNull(prop);
-        assertEquals(20, prop.size());
+        assertMockEndpointsSatisfied();
+    }
 
-        assertEquals("{{cool.b}}", prop.getProperty("cool.a"));
-        assertEquals("10", prop.getProperty("myQueueSize"));
-        assertEquals("true", prop.getProperty("integration.ftpEnabled"));
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .setBody().constant("\\{{before\\}}{{cool.concat.escaped}}\\{{after\\}}")
+                        .setHeader("foo")
+                        .constant(
+                                "Hello {{cool.concat.escaped}} and \\{{before\\}}{{cool.other.name}}\\{{after\\}}/\\\\{{cool.other.name}} how are you?")
+                        .to("mock:result");
+            }
+        };
     }
 
     @Override
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java
index 211bec4234f..ea786c20d7b 100644
--- a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentLoadPropertiesTest.java
@@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-public class PropertiesComponentLoadPropertiesTest extends ContextTestSupport {
+class PropertiesComponentLoadPropertiesTest extends ContextTestSupport {
 
     @Override
     public boolean isUseRouteBuilder() {
@@ -33,14 +33,14 @@ public class PropertiesComponentLoadPropertiesTest extends ContextTestSupport {
     }
 
     @Test
-    public void testLoadProperties() throws Exception {
+    void testLoadProperties() {
         context.start();
 
         org.apache.camel.spi.PropertiesComponent pc = context.getPropertiesComponent();
         Properties prop = pc.loadProperties();
 
         assertNotNull(prop);
-        assertEquals(20, prop.size());
+        assertEquals(21, prop.size());
 
         assertEquals("{{cool.b}}", prop.getProperty("cool.a"));
         assertEquals("10", prop.getProperty("myQueueSize"));
diff --git a/core/camel-core/src/test/resources/org/apache/camel/component/properties/myproperties.properties b/core/camel-core/src/test/resources/org/apache/camel/component/properties/myproperties.properties
index fd499be4ca9..f5af7d386ea 100644
--- a/core/camel-core/src/test/resources/org/apache/camel/component/properties/myproperties.properties
+++ b/core/camel-core/src/test/resources/org/apache/camel/component/properties/myproperties.properties
@@ -20,6 +20,7 @@ cool.result=result
 cool.result.xx=result
 cool.end.xx=mock:result
 cool.concat=mock:{{cool.result}}
+cool.concat.escaped=mock:\\{{cool.result\\}}{"query":{"match_all":{}\\}}
 cool.start=direct:cool
 cool.showid=true
 cool.name=Camel