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 2022/12/08 09:00:41 UTC

[camel] branch main updated: CAMEL-18802: properties function to support looking up key first before apply function. base64 function needs this.

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

davsclaus 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 7c8e2ef8d72 CAMEL-18802: properties function to support looking up key first before apply function. base64 function needs this.
7c8e2ef8d72 is described below

commit 7c8e2ef8d72def4f82bfe04fd6c3d962ce7d02ae
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Dec 8 10:00:18 2022 +0100

    CAMEL-18802: properties function to support looking up key first before apply function. base64 function needs this.
---
 .../base64/Base64PropertiesFunction.java           |   8 +
 ...ava => Base64PropertiesFunctionDecodeTest.java} |   4 +-
 ...a => Base64PropertiesFunctionOptionalTest.java} |  10 +-
 .../base64/Base64PropertiesFunctionTest.java       |   8 +-
 .../org/apache/camel/spi/PropertiesFunction.java   |  16 +-
 .../properties/DefaultPropertiesParser.java        |  13 ++
 ...rtyFunctionOptionalPropertyPlaceholderTest.java | 229 +++++++++++++++++++++
 .../ROOT/pages/camel-3x-upgrade-guide-3_20.adoc    |  18 ++
 8 files changed, 294 insertions(+), 12 deletions(-)

diff --git a/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java b/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
index 4eedd37c8dd..b3109cc4144 100644
--- a/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
+++ b/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
@@ -35,8 +35,16 @@ public class Base64PropertiesFunction implements PropertiesFunction {
         return "base64";
     }
 
+    @Override
+    public boolean lookupFirst(String remainder) {
+        return !remainder.startsWith("decode:");
+    }
+
     @Override
     public String apply(String remainder) {
+        if (remainder.startsWith("decode:")) {
+            remainder = remainder.substring(7);
+        }
         byte[] arr = codec.decode(remainder);
         return new String(arr);
     }
diff --git a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionDecodeTest.java
similarity index 91%
copy from components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
copy to components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionDecodeTest.java
index 2121b2e36ab..865b194e8ac 100644
--- a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
+++ b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionDecodeTest.java
@@ -22,7 +22,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.junit5.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 
-public class Base64PropertiesFunctionTest extends CamelTestSupport {
+public class Base64PropertiesFunctionDecodeTest extends CamelTestSupport {
 
     @Test
     public void testBase64() throws Exception {
@@ -40,7 +40,7 @@ public class Base64PropertiesFunctionTest extends CamelTestSupport {
             public void configure() {
                 from("direct:start")
                         // Q2FtZWw== is the word Camel
-                        .setBody(simple("${body} {{base64:Q2FtZWw==}}"))
+                        .setBody(simple("${body} {{base64:decode:Q2FtZWw==}}"))
                         .to("mock:result");
             }
         };
diff --git a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionOptionalTest.java
similarity index 85%
copy from components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
copy to components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionOptionalTest.java
index 2121b2e36ab..3aff51ebbdd 100644
--- a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
+++ b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionOptionalTest.java
@@ -22,11 +22,11 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.junit5.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 
-public class Base64PropertiesFunctionTest extends CamelTestSupport {
+public class Base64PropertiesFunctionOptionalTest extends CamelTestSupport {
 
     @Test
-    public void testBase64() throws Exception {
-        getMockEndpoint("mock:result").expectedBodiesReceived("Hello Camel");
+    public void testBase64Key() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("Hello");
 
         template.sendBody("direct:start", "Hello");
 
@@ -38,9 +38,9 @@ public class Base64PropertiesFunctionTest extends CamelTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() {
+                // there is no fooKey
                 from("direct:start")
-                        // Q2FtZWw== is the word Camel
-                        .setBody(simple("${body} {{base64:Q2FtZWw==}}"))
+                        .setBody(simple("${body} {{base64:?fooKey}}"))
                         .to("mock:result");
             }
         };
diff --git a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
index 2121b2e36ab..5c392fb9f0c 100644
--- a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
+++ b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
@@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test;
 public class Base64PropertiesFunctionTest extends CamelTestSupport {
 
     @Test
-    public void testBase64() throws Exception {
+    public void testBase64Key() throws Exception {
         getMockEndpoint("mock:result").expectedBodiesReceived("Hello Camel");
 
         template.sendBody("direct:start", "Hello");
@@ -38,9 +38,11 @@ public class Base64PropertiesFunctionTest extends CamelTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() {
+                // Q2FtZWw== is the word Camel
+                context.getPropertiesComponent().addInitialProperty("fooKey", "Q2FtZWw==");
+
                 from("direct:start")
-                        // Q2FtZWw== is the word Camel
-                        .setBody(simple("${body} {{base64:Q2FtZWw==}}"))
+                        .setBody(simple("${body} {{base64:fooKey}}"))
                         .to("mock:result");
             }
         };
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesFunction.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesFunction.java
index 665218ba309..85dc86eeaa5 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesFunction.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesFunction.java
@@ -22,16 +22,28 @@ package org.apache.camel.spi;
 public interface PropertiesFunction {
 
     /**
-     * Name of the function which is used as <tt>name:</tt> to let the properties component know its a function.
+     * Name of the function which is used as <tt>name:</tt> to let the properties component know it is a function.
      */
     String getName();
 
     /**
-     * Applies the function
+     * Applies the function.
      *
      * @param  remainder the remainder value
      * @return           a value as the result of the function
+     * @see #lookupFirst(String)
      */
     String apply(String remainder);
 
+    /**
+     * Whether the value should be looked up as a regular properties first, before applying this function.
+     *
+     * @param  remainder the remainder value
+     * @return           true to resolve the remainder value as a property value, and then afterwards apply this function,
+     *                   false to apply this function without lookup (default).
+     */
+    default boolean lookupFirst(String remainder) {
+        return false;
+    }
+
 }
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 80b52b0518c..8c7f00b17e5 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
@@ -313,6 +313,19 @@ public class DefaultPropertiesParser implements PropertiesParser {
                 PropertiesFunction function = propertiesComponent.getPropertiesFunction(prefix);
                 if (function != null) {
                     String remainder = StringHelper.after(key, ":");
+                    if (function.lookupFirst(remainder)) {
+                        boolean optional = remainder != null && remainder.startsWith(OPTIONAL_TOKEN);
+                        String value = getPropertyValue(remainder, input);
+                        if (optional && value == null) {
+                            return null;
+                        }
+                        // it was not possible to resolve
+                        if (value != null && value.startsWith(UNRESOLVED_PREFIX_TOKEN)) {
+                            return value;
+                        } else {
+                            remainder = value;
+                        }
+                    }
                     log.debug("Property with key [{}] is applied by function [{}]", key, function.getName());
                     String value = function.apply(remainder);
                     if (value == null) {
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertyFunctionOptionalPropertyPlaceholderTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertyFunctionOptionalPropertyPlaceholderTest.java
new file mode 100644
index 00000000000..abb0396bd18
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertyFunctionOptionalPropertyPlaceholderTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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 java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.PropertiesFunction;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PropertyFunctionOptionalPropertyPlaceholderTest extends ContextTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testNoFunctionNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .setBody().constant("{{?myKey}}")
+                        .to("mock:result");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+        getMockEndpoint("mock:result").allMessages().body().isNull();
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testNoFunctionPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("myKey", "123");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .setBody().constant("{{?myKey}}")
+                        .to("mock:result");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedBodiesReceived("123", "123");
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testFunctionNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .setBody().constant("{{reverse:?myKey}}")
+                        .to("mock:result");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+        getMockEndpoint("mock:result").allMessages().body().isNull();
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testFunctionPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("myKey", "123");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .setBody().constant("{{reverse:?myKey}}")
+                        .to("mock:result");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedBodiesReceived("321", "321");
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testKeepUnresolved() throws Exception {
+        String out = context.adapt(ExtendedCamelContext.class)
+                .resolvePropertyPlaceholders("{{reverse:?myKey}}", true);
+        Assertions.assertEquals("{{?myKey}}", out);
+    }
+
+    @Test
+    public void testQueryOptionalNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:result?retainFirst={{reverse:?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryOptionalPresent() throws Exception {
+        context.getPropertiesComponent().addInitialProperty("maxKeep", "321");
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:result?retainFirst={{reverse:?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result?retainFirst=123").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result?retainFirst=123").getReceivedExchanges().size());
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getPropertiesComponent().setLocation("classpath:org/apache/camel/component/properties/myproperties.properties");
+        ReverseFunction func = new ReverseFunction();
+        func.setCamelContext(context);
+        context.getPropertiesComponent().addPropertiesFunction(func);
+        return context;
+    }
+
+    private static class ReverseFunction implements PropertiesFunction, CamelContextAware {
+
+        private CamelContext camelContext;
+
+        @Override
+        public CamelContext getCamelContext() {
+            return camelContext;
+        }
+
+        @Override
+        public void setCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+        }
+
+        @Override
+        public String getName() {
+            return "reverse";
+        }
+
+        @Override
+        public boolean lookupFirst(String remainder) {
+            return true;
+        }
+
+        @Override
+        public String apply(String remainder) {
+            if (remainder == null || remainder.isEmpty()) {
+                return remainder;
+            }
+            StringBuilder sb = new StringBuilder(remainder);
+            return sb.reverse().toString();
+        }
+    }
+
+}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_20.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_20.adoc
index a5bbdbf619c..97b71823006 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_20.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_20.adoc
@@ -27,6 +27,24 @@ from `<groupdId>org.apache.camel</groupdId>` to `<groupdId>org.apache.camel.mave
 
 Dependencies not intended for end users has been removed, such as all `-parent` JARs.
 
+=== camel-base64
+
+The `base64` property placeholder function will now lookup the value as a property key.
+For example
+
+[source,text]
+----
+{{base64:myKey}}
+----
+
+Will now look up myKey as a property placeholder value, which then is decoded.
+If you want to decode the value as-is, then use `base64:decode:` as shown below:
+
+[source,text]
+----
+{{base64:decode:Q2FtZWw==}}
+----
+
 === camel-log
 
 The log component now shows cached streams (`org.apache.camel.StreamCache`) message bodies by default.