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/11 17:58:02 UTC

[camel] branch CAMEL-18576/allow-to-escape-double-braces created (now e10a74a037b)

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

nfilotto pushed a change to branch CAMEL-18576/allow-to-escape-double-braces
in repository https://gitbox.apache.org/repos/asf/camel.git


      at e10a74a037b CAMEL-18576: camel-core - Escape a placeholder

This branch includes the following new commits:

     new e10a74a037b CAMEL-18576: camel-core - Escape a placeholder

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[camel] 01/01: CAMEL-18576: camel-core - Escape a placeholder

Posted by nf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nfilotto pushed a commit to branch CAMEL-18576/allow-to-escape-double-braces
in repository https://gitbox.apache.org/repos/asf/camel.git

commit e10a74a037b041186361df9312b09adf71f555d6
Author: Nicolas Filotto <nf...@talend.com>
AuthorDate: Tue Oct 11 19:56:48 2022 +0200

    CAMEL-18576: camel-core - Escape a placeholder
---
 .../src/main/docs/properties-component.adoc        |  7 +++
 .../properties/DefaultPropertiesParser.java        | 30 +++++++++--
 .../component/properties/PropertiesComponent.java  | 19 +++++++
 .../properties/PropertiesComponentEscapedTest.java | 59 ++++++++++++++++++++++
 .../component/properties/myproperties.properties   |  1 +
 5 files changed, 111 insertions(+), 5 deletions(-)

diff --git a/core/camel-base/src/main/docs/properties-component.adoc b/core/camel-base/src/main/docs/properties-component.adoc
index 928b5461454..fa1e0155eae 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..94a6a3f688a 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, PREFIX_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 of the token
+         * @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 da06caa6b2e..25ea59d2261 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
@@ -24,6 +24,7 @@ import java.util.Optional;
 import java.util.Properties;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import org.apache.camel.CamelContext;
@@ -105,6 +106,11 @@ public class PropertiesComponent extends ServiceSupport
 
     private static final String NEGATE_PREFIX = PREFIX_TOKEN + "!";
 
+    /**
+     * The regular expression representing an escaped {@link #PREFIX_TOKEN} or {@link #SUFFIX_TOKEN}.
+     */
+    private static final Pattern ESCAPED_TOKEN_PATTERN = Pattern.compile("([^\\\\]?)\\\\(\\{\\{|}})");
+
     private CamelContext camelContext;
     private PropertiesFunctionResolver propertiesFunctionResolver = new DefaultPropertiesFunctionResolver();
     private PropertiesParser propertiesParser = new DefaultPropertiesParser(this);
@@ -320,6 +326,10 @@ public class PropertiesComponent extends ServiceSupport
                 answer = "true";
             }
         }
+        if (answer != null) {
+            // Remove the escaped characters
+            answer = unescape(answer);
+        }
         LOG.trace("Parsed uri {} -> {}", uri, answer);
         return answer;
     }
@@ -832,4 +842,13 @@ public class PropertiesComponent extends ServiceSupport
         return answer;
     }
 
+    /**
+     * Replaces all the double braces that have been escaped by double braces.
+     *
+     * @param  input the content to unescape
+     * @return       the provided content with all the escaped double braces restored.
+     */
+    private static String unescape(String input) {
+        return ESCAPED_TOKEN_PATTERN.matcher(input).replaceAll("$1$2");
+    }
 }
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java
new file mode 100644
index 00000000000..274e4830b2d
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentEscapedTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.component.properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+class PropertiesComponentEscapedTest extends ContextTestSupport {
+
+    @Test
+    void testEscaped() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("{{before}}mock:{{cool.result}}{{after}}");
+        getMockEndpoint("mock:result").expectedHeaderReceived("foo",
+                "Hello mock:{{cool.result}} and {{before}}Cheese{{after}}/\\Cheese how are you?");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @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
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getPropertiesComponent().setLocation("classpath:org/apache/camel/component/properties/myproperties.properties");
+        return context;
+    }
+
+}
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..11a31637a0c 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\\}}
 cool.start=direct:cool
 cool.showid=true
 cool.name=Camel