You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/03/25 08:41:18 UTC

[camel] branch master updated: CAMEL-16242: camel-core - Language expressions with single quoted property placeholder values should be replaced as double quotes to fix the O'Niel problem and other inputs that can use single quotes in names/text.

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

davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/master by this push:
     new c55e70c  CAMEL-16242: camel-core - Language expressions with single quoted property placeholder values should be replaced as double quotes to fix the O'Niel problem and other inputs that can use single quotes in names/text.
c55e70c is described below

commit c55e70cd692797cbf271ac5079553db9e2092269
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Mar 25 09:37:28 2021 +0100

    CAMEL-16242: camel-core - Language expressions with single quoted property placeholder values should be replaced as double quotes to fix the O'Niel problem and other inputs that can use single quotes in names/text.
---
 .../apache/camel/jsonpath/JsonPathSplitTest.java   |  2 +-
 .../JsonPathTransformONielEscapedTest.java         |  2 +-
 ... => JsonPathTransformONielPlaceholderTest.java} | 24 ++++++++++--
 .../camel/jsonpath/JsonPathTransformONielTest.java |  2 +-
 .../camel-jsonpath/src/test/resources/books.json   |  4 +-
 .../camel/reifier/language/ExpressionReifier.java  | 43 ++++++++++++++++++++++
 ...st.java => XPathFunctionsONielProblemTest.java} | 10 ++---
 .../camel/builder/xml/XPathFunctionsTest.java      |  2 +-
 8 files changed, 75 insertions(+), 14 deletions(-)

diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitTest.java
index a392985..1e996f4 100644
--- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitTest.java
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitTest.java
@@ -75,7 +75,7 @@ public class JsonPathSplitTest extends CamelTestSupport {
         // should preserve quotes etc
         assertTrue(out.contains("\"author\": \"Nigel Rees\""));
         assertTrue(out.contains("\"price\": 8.95"));
-        assertTrue(out.contains("\"title\": \"Sword of Honour\""));
+        assertTrue(out.contains("\"title\": \"Sword's of Honour\""));
         assertTrue(out.contains("\"price\": 12.99,"));
     }
 
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielEscapedTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielEscapedTest.java
index 8031a2f..95fe395 100644
--- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielEscapedTest.java
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielEscapedTest.java
@@ -48,7 +48,7 @@ public class JsonPathTransformONielEscapedTest extends CamelTestSupport {
         assertMockEndpointsSatisfied();
 
         List<?> titles = getMockEndpoint("mock:authors").getReceivedExchanges().get(0).getIn().getBody(List.class);
-        assertEquals("Camel in Space", titles.get(0));
+        assertEquals("Camels in Space", titles.get(0));
     }
 
 }
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielPlaceholderTest.java
similarity index 69%
copy from components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java
copy to components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielPlaceholderTest.java
index a9543c7..08f4c3b 100644
--- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielPlaceholderTest.java
@@ -18,14 +18,30 @@ package org.apache.camel.jsonpath;
 
 import java.io.File;
 import java.util.List;
+import java.util.Properties;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.test.junit5.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-public class JsonPathTransformONielTest extends CamelTestSupport {
+public class JsonPathTransformONielPlaceholderTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        PropertiesComponent pc = context.getPropertiesComponent();
+        Properties props = new Properties();
+        props.put("who", "John O'Niel");
+        props.put("search", "Sword's of Honour");
+        pc.setInitialProperties(props);
+
+        return context;
+    }
 
     @Override
     protected RouteBuilder createRouteBuilder() throws Exception {
@@ -33,7 +49,7 @@ public class JsonPathTransformONielTest extends CamelTestSupport {
             @Override
             public void configure() throws Exception {
                 from("direct:start")
-                        .transform().jsonpath("$.store.book[?(@.author == \"John O'Niel\")].title")
+                        .transform().jsonpath("$.store.book[?(@.author == '{{who}}' || @.title == '{{search}}')].title")
                         .to("mock:authors");
             }
         };
@@ -48,7 +64,9 @@ public class JsonPathTransformONielTest extends CamelTestSupport {
         assertMockEndpointsSatisfied();
 
         List<?> titles = getMockEndpoint("mock:authors").getReceivedExchanges().get(0).getIn().getBody(List.class);
-        assertEquals("Camel in Space", titles.get(0));
+        assertEquals(2, titles.size());
+        assertEquals("Sword's of Honour", titles.get(0));
+        assertEquals("Camels in Space", titles.get(1));
     }
 
 }
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java
index a9543c7..75c5b9f 100644
--- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformONielTest.java
@@ -48,7 +48,7 @@ public class JsonPathTransformONielTest extends CamelTestSupport {
         assertMockEndpointsSatisfied();
 
         List<?> titles = getMockEndpoint("mock:authors").getReceivedExchanges().get(0).getIn().getBody(List.class);
-        assertEquals("Camel in Space", titles.get(0));
+        assertEquals("Camels in Space", titles.get(0));
     }
 
 }
diff --git a/components/camel-jsonpath/src/test/resources/books.json b/components/camel-jsonpath/src/test/resources/books.json
index a412426..ff2a384 100644
--- a/components/camel-jsonpath/src/test/resources/books.json
+++ b/components/camel-jsonpath/src/test/resources/books.json
@@ -10,14 +10,14 @@
             {
                 "category": "fiction",
                 "author": "Evelyn Waugh",
-                "title": "Sword of Honour",
+                "title": "Sword's of Honour",
                 "price": 12.99,
                 "isbn": "0-553-21311-3"
             },
             {
                 "category": "fiction",
                 "author": "John O'Niel",
-                "title": "Camel in Space",
+                "title": "Camels in Space",
                 "price": 9.99,
                 "isbn": "0-555-12345-6"
             }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
index 0fb98a5..44aca6b 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
@@ -19,6 +19,8 @@ package org.apache.camel.reifier.language;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.BiFunction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.camel.AfterPropertiesConfigured;
 import org.apache.camel.CamelContext;
@@ -50,6 +52,7 @@ import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
 import org.apache.camel.reifier.AbstractReifier;
 import org.apache.camel.spi.Language;
+import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.spi.PropertyConfigurer;
 import org.apache.camel.spi.PropertyConfigurerAware;
 import org.apache.camel.spi.ReifierStrategy;
@@ -60,6 +63,8 @@ import org.apache.camel.util.ObjectHelper;
 
 public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractReifier {
 
+    private static final Pattern SINGLE_TO_DOUBLE = Pattern.compile("'(\\{\\{.*?}})'"); // non-greedy mode
+
     // for custom reifiers
     private static final Map<Class<?>, BiFunction<CamelContext, ExpressionDefinition, ExpressionReifier<? extends ExpressionDefinition>>> EXPRESSIONS
             = new HashMap<>(0);
@@ -165,6 +170,8 @@ public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractR
     public Expression createExpression() {
         Expression expression = definition.getExpressionValue();
         if (expression == null) {
+            // prepare before creating
+            prepareExpression();
             if (definition.getExpressionType() != null) {
                 expression = reifier(camelContext, definition.getExpressionType()).createExpression();
             } else {
@@ -201,6 +208,8 @@ public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractR
     public Predicate createPredicate() {
         Predicate predicate = definition.getPredicate();
         if (predicate == null) {
+            // prepare before creating
+            prepareExpression();
             if (definition.getExpressionType() != null) {
                 predicate = reifier(camelContext, definition.getExpressionType()).createPredicate();
             } else if (definition.getExpressionValue() != null) {
@@ -265,6 +274,40 @@ public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractR
         }
     }
 
+    /**
+     * Prepares the expression/predicate before being created by the reifier
+     */
+    protected void prepareExpression() {
+        // when using languages with property placeholders then we have a single vs double quote problem
+        // where it may be common to use single quote inside a Java string, eg
+        // "${header.name} == '{{who}}'"
+        // and then the who property placeholder may contain a single quote such as John O'Niel which
+        // is extrapolated as "${header.name} == 'John O'Niel'" which causes a parsing problem
+        // so what Camel does is to replace all '{{key}}' placeholders with double quoted instead
+        // that resolves the parsing problem
+
+        String text = definition.getExpression();
+        if (text != null && text.contains(PropertiesComponent.PREFIX_TOKEN)) {
+            boolean changed = false;
+            Matcher matcher = SINGLE_TO_DOUBLE.matcher(text);
+            while (matcher.find()) {
+                String group = matcher.group(1);
+                // is there a single quote in the resolved placeholder
+                String resolved = camelContext.resolvePropertyPlaceholders(group);
+                if (resolved != null && resolved.indexOf('\'') != -1) {
+                    // replace single quoted with double quoted
+                    text = matcher.replaceFirst("\"$1\"");
+                    // we changed so reset matcher so it can find more
+                    matcher.reset(text);
+                    changed = true;
+                }
+            }
+            if (changed) {
+                definition.setExpression(text);
+            }
+        }
+    }
+
     @Deprecated
     protected void setProperties(Object target, Map<String, Object> properties) {
         properties.entrySet().removeIf(e -> e.getValue() == null);
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsONielProblemTest.java
similarity index 91%
copy from core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java
copy to core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsONielProblemTest.java
index a1e78b1..b92252b 100644
--- a/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsONielProblemTest.java
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
 /**
  * XPath with and without header test.
  */
-public class XPathFunctionsTest extends ContextTestSupport {
+public class XPathFunctionsONielProblemTest extends ContextTestSupport {
 
     @Test
     public void testChoiceWithHeaderAndPropertiesSelectCamel() throws Exception {
@@ -40,9 +40,9 @@ public class XPathFunctionsTest extends ContextTestSupport {
     @Test
     public void testChoiceWithNoHeaderAndPropertiesSelectDonkey() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:donkey");
-        mock.expectedBodiesReceived("<name>Donkey Kong</name>");
+        mock.expectedBodiesReceived("<name>Kong</name>");
 
-        template.sendBody("direct:in", "<name>Donkey Kong</name>");
+        template.sendBody("direct:in", "<name>Kong</name>");
 
         mock.assertIsSatisfied();
     }
@@ -73,12 +73,12 @@ public class XPathFunctionsTest extends ContextTestSupport {
                         // $type is a variable for the header with key type
                         // here we use the properties function to lookup foo from
                         // the properties files
-                        // which at runtime will be evaluted to 'Camel'
+                        // which at runtime will be evaluated to 'Camel'
                         .when().xpath("$type = function:properties('foo')").to("mock:camel")
                         // here we use the simple language to evaluate the
                         // expression
                         // which at runtime will be evaluated to 'Donkey Kong'
-                        .when().xpath("//name = function:simple('Donkey {{bar}}')").to("mock:donkey").otherwise()
+                        .when().xpath("//name = function:simple('{{bar}}')").to("mock:donkey").otherwise()
                         .to("mock:other").end();
                 // END SNIPPET: ex
             }
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java
index a1e78b1..3abf21f 100644
--- a/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/xml/XPathFunctionsTest.java
@@ -73,7 +73,7 @@ public class XPathFunctionsTest extends ContextTestSupport {
                         // $type is a variable for the header with key type
                         // here we use the properties function to lookup foo from
                         // the properties files
-                        // which at runtime will be evaluted to 'Camel'
+                        // which at runtime will be evaluated to 'Camel'
                         .when().xpath("$type = function:properties('foo')").to("mock:camel")
                         // here we use the simple language to evaluate the
                         // expression