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 5dc243fa0462ed8fa1672baea7886fbeabac3ef4
Author: Nicolas Filotto <nfilo...@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..c873c41048c 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 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

Reply via email to