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 <essob...@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