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 2023/12/16 08:07:34 UTC

(camel) branch main updated: CAMEL-18082: camel-jbang - Camel run in prompt mode to ask for requir… (#12461)

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 2c8a2741771 CAMEL-18082: camel-jbang - Camel run in prompt mode to ask for requir… (#12461)
2c8a2741771 is described below

commit 2c8a27417712aceb9050e6c49d3ed91a8ad22a68
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Dec 16 09:07:27 2023 +0100

    CAMEL-18082: camel-jbang - Camel run in prompt mode to ask for requir… (#12461)
    
    * CAMEL-18082: camel-jbang - Camel run in prompt mode to ask for required values. Adjust properties component API to allow access to default value in custom properties lookup and sources.
    
    * CAMEL-18082: camel-jbang - Camel run in prompt mode to ask for required values. Adjust properties component API to allow access to default value in custom properties lookup and sources.
---
 .../spi/BridgePropertyPlaceholderConfigurer.java   |  2 +-
 .../org/apache/camel/spi/PropertiesSource.java     | 11 ++++
 .../properties/DefaultPropertiesLookup.java        | 21 ++++----
 .../properties/DefaultPropertiesParser.java        | 20 ++++----
 .../component/properties/PropertiesLookup.java     |  7 +--
 .../ROOT/pages/camel-4x-upgrade-guide-4_4.adoc     |  2 +
 .../modules/ROOT/pages/camel-jbang.adoc            | 59 ++++++++++++++++++++++
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  7 +++
 .../java/org/apache/camel/main/KameletMain.java    |  6 +++
 .../download/PromptPropertyPlaceholderSource.java  | 55 ++++++++++++++++++++
 10 files changed, 166 insertions(+), 24 deletions(-)

diff --git a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
index 753c35d348f..53c68e96509 100644
--- a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
+++ b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
@@ -180,7 +180,7 @@ public class BridgePropertyPlaceholderConfigurer extends PropertyPlaceholderConf
                 propVal = resolveSystemProperty(placeholderName);
             }
             if (propVal == null) {
-                propVal = properties.lookup(placeholderName);
+                propVal = properties.lookup(placeholderName, null);
             }
             if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
                 propVal = resolveSystemProperty(placeholderName);
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java
index 7daa12ad236..0e0a6ff3db0 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java
@@ -39,4 +39,15 @@ public interface PropertiesSource {
      */
     String getProperty(String name);
 
+    /**
+     * Gets the property with the name
+     *
+     * @param  name         name of property
+     * @param  defaultValue default value to use as fallback
+     * @return              the property value, or <tt>null</tt> if no property exists
+     */
+    default String getProperty(String name, String defaultValue) {
+        return getProperty(name);
+    }
+
 }
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
index adf7602be79..629584e0e03 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
@@ -38,15 +38,15 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
     }
 
     @Override
-    public String lookup(String name) {
+    public String lookup(String name, String defaultValue) {
         try {
-            return doLookup(name);
+            return doLookup(name, defaultValue);
         } catch (NoTypeConversionAvailableException e) {
             throw RuntimeCamelException.wrapRuntimeCamelException(e);
         }
     }
 
-    private String doLookup(String name) throws NoTypeConversionAvailableException {
+    private String doLookup(String name, String defaultValue) throws NoTypeConversionAvailableException {
         String answer = null;
 
         // local takes precedence
@@ -57,14 +57,15 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
             if (value != null) {
                 answer = component.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, value);
                 String loc = location(local, name, "LocalProperties");
-                String defaultValue = null;
+                String localDefaultValue = null;
                 if (local instanceof OrderedLocationProperties) {
                     Object val = ((OrderedLocationProperties) local).getDefaultValue(name);
                     if (val != null) {
-                        defaultValue = component.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, val);
+                        localDefaultValue
+                                = component.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, val);
                     }
                 }
-                onLookup(name, answer, defaultValue, loc);
+                onLookup(name, answer, localDefaultValue, loc);
             }
         }
 
@@ -75,13 +76,13 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
             if (value != null) {
                 answer = component.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, value);
                 String loc = location(local, name, "OverrideProperties");
-                onLookup(name, answer, null, loc);
+                onLookup(name, answer, defaultValue, loc);
             }
         }
         if (answer == null) {
             // try till first found source
             for (PropertiesSource ps : component.getPropertiesSources()) {
-                answer = ps.getProperty(name);
+                answer = ps.getProperty(name, defaultValue);
                 if (answer != null) {
                     String source = ps.getName();
                     if (ps instanceof ClasspathPropertiesSource) {
@@ -99,7 +100,7 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
                             source = olp.getLocation(name);
                         }
                     }
-                    onLookup(name, answer, null, source);
+                    onLookup(name, answer, defaultValue, source);
                     break;
                 }
             }
@@ -111,7 +112,7 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
             if (value != null) {
                 answer = component.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, value);
                 String loc = location(local, name, "InitialProperties");
-                onLookup(name, answer, null, loc);
+                onLookup(name, answer, defaultValue, loc);
             }
         }
 
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 e74100846fe..4d920df356d 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
@@ -355,7 +355,7 @@ public class DefaultPropertiesParser implements PropertiesParser {
                 key = key.substring(OPTIONAL_TOKEN.length());
             }
 
-            String value = doGetPropertyValue(key);
+            String value = doGetPropertyValue(key, defaultValue);
             if (value == null && defaultValue != null) {
                 log.debug("Property with key [{}] not found, using default value: {}", key, defaultValue);
                 value = defaultValue;
@@ -390,7 +390,7 @@ public class DefaultPropertiesParser implements PropertiesParser {
          * @param  key Key of the property
          * @return     Value of the property or {@code null} if not found
          */
-        private String doGetPropertyValue(String key) {
+        private String doGetPropertyValue(String key, String defaultValue) {
             if (ObjectHelper.isEmpty(key)) {
                 return parseProperty(key, null, properties);
             }
@@ -402,16 +402,16 @@ public class DefaultPropertiesParser implements PropertiesParser {
             if (local != null) {
                 value = local.getProperty(key);
                 if (value != null) {
-                    String defaultValue = null;
+                    String localDefaultValue = null;
                     String loc = location(local, key, "LocalProperties");
                     if (local instanceof OrderedLocationProperties) {
                         Object val = ((OrderedLocationProperties) local).getDefaultValue(key);
                         if (val != null) {
-                            defaultValue
+                            localDefaultValue
                                     = propertiesComponent.getCamelContext().getTypeConverter().tryConvertTo(String.class, val);
                         }
                     }
-                    onLookup(key, value, defaultValue, loc);
+                    onLookup(key, value, localDefaultValue, loc);
                     log.debug("Found local property: {} with value: {} to be used.", key, value);
                 }
             }
@@ -427,20 +427,20 @@ public class DefaultPropertiesParser implements PropertiesParser {
             if (value == null && envMode == PropertiesComponent.ENVIRONMENT_VARIABLES_MODE_OVERRIDE) {
                 value = lookupEnvironmentVariable(key);
                 if (value != null) {
-                    onLookup(key, value, null, "ENV");
+                    onLookup(key, value, defaultValue, "ENV");
                     log.debug("Found an OS environment property: {} with value: {} to be used.", key, value);
                 }
             }
             if (value == null && sysMode == PropertiesComponent.SYSTEM_PROPERTIES_MODE_OVERRIDE) {
                 value = System.getProperty(key);
                 if (value != null) {
-                    onLookup(key, value, null, "SYS");
+                    onLookup(key, value, defaultValue, "SYS");
                     log.debug("Found a JVM system property: {} with value: {} to be used.", key, value);
                 }
             }
 
             if (value == null && properties != null) {
-                value = properties.lookup(key);
+                value = properties.lookup(key, defaultValue);
                 if (value != null) {
                     log.debug("Found property: {} with value: {} to be used.", key, value);
                 }
@@ -457,14 +457,14 @@ public class DefaultPropertiesParser implements PropertiesParser {
             if (value == null && envMode == PropertiesComponent.ENVIRONMENT_VARIABLES_MODE_FALLBACK) {
                 value = lookupEnvironmentVariable(key);
                 if (value != null) {
-                    onLookup(key, value, null, "ENV");
+                    onLookup(key, value, defaultValue, "ENV");
                     log.debug("Found an OS environment property: {} with value: {} to be used.", key, value);
                 }
             }
             if (value == null && sysMode == PropertiesComponent.SYSTEM_PROPERTIES_MODE_FALLBACK) {
                 value = System.getProperty(key);
                 if (value != null) {
-                    onLookup(key, value, null, "SYS");
+                    onLookup(key, value, defaultValue, "SYS");
                     log.debug("Found a JVM system property: {} with value: {} to be used.", key, value);
                 }
             }
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesLookup.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesLookup.java
index ed629f84bdd..0c8aa15350d 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesLookup.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesLookup.java
@@ -25,9 +25,10 @@ public interface PropertiesLookup {
     /**
      * Lookup the property with the given name
      *
-     * @param  name property name
-     * @return      the property value, or <tt>null</tt> if the properties does not exist.
+     * @param  name         property name
+     * @param  defaultValue default value for the property (if any exists)
+     * @return              the property value, or <tt>null</tt> if the properties does not exist.
      */
-    String lookup(String name);
+    String lookup(String name, String defaultValue);
 
 }
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
index 050676bb9de..51cdc9b460a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
@@ -13,6 +13,8 @@ use the default constructor if necessary.
 
 The method `getCreated` is now deprecated. Access to the time-related information from the exchange should be done via `getClock`.
 
+The `lookup` method in `org.apache.camel.component.properties.PropertiesLookup` now has a 2nd parameter for the default value.
+
 === camel-azure-cosmosdb
 
 The useDefaultIdentity parameter has been removed in favor of the credentialType parameter. Now user should select between SHARED_ACCOUNT_KEY and AZURE_IDENTITY.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index fc9e5a96e25..6fb9d21c388 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -209,6 +209,65 @@ Camel JBang will then scan in `src/main/java` and `src/main/resources` for files
 
 NOTE: Using `camel run pom.xml` is not intended as a fully compatible way of running an existing Maven based project.
 
+=== Running Route with user interactive prompt for placeholder values
+
+You can create Camel integrations that makes it possible for the user to quickly enter placeholder values from command prompt.
+
+For example given the following route:
+
+[source,java]
+----
+import org.apache.camel.builder.RouteBuilder;
+
+public class foo extends RouteBuilder {
+
+    @Override
+    public void configure() throws Exception {
+        from("timer:java?period={{time:1000}}")
+            .setBody()
+                .simple("Hello Camel from {{you}}")
+            .log("${body}");
+    }
+}
+----
+
+Then if you run this with:
+
+[source,bash]
+----
+camel run foo.java
+----
+
+You will have an exception on startup about the missing value
+`Caused by: java.lang.IllegalArgumentException: Property with key [you] not found in properties from text: Hello Camel from {{you}}`
+
+However, you can then run in prompt mode as follows:
+
+[source,bash]
+----
+camel run foo.java --prompt
+----
+
+And Camel will now prompt in the terminal for you to enter values for the placeholders:
+
+[source,bash]
+----
+2023-12-15 21:46:44.218  INFO 15033 --- [           main] org.apache.camel.main.MainSupport   : Apache Camel (JBang) 4.3.0-SNAPSHOT is starting
+2023-12-15 21:46:44.331  INFO 15033 --- [           main] org.apache.camel.main.MainSupport   : Using Java 17.0.5 with PID 15033. Started by davsclaus in /Users/davsclaus/workspace/deleteme/prompt
+2023-12-15 21:46:45.360  INFO 15033 --- [           main] mel.cli.connector.LocalCliConnector : Management from Camel JBang enabled
+Enter optional value for time (1000):
+Enter required value for you: Jack
+2023-12-15 21:46:55.239  INFO 15033 --- [           main] el.impl.engine.AbstractCamelContext : Apache Camel 4.3.0-SNAPSHOT (foo) is starting
+2023-12-15 21:46:55.323  INFO 15033 --- [           main] g.apache.camel.main.BaseMainSupport : Property-placeholders summary
+2023-12-15 21:46:55.323  INFO 15033 --- [           main] g.apache.camel.main.BaseMainSupport :     [prompt]                       you=Jack
+2023-12-15 21:46:55.341  INFO 15033 --- [           main] el.impl.engine.AbstractCamelContext : Routes startup (started:1)
+----
+
+From the snippet above, Camel JBang had two prompts. First for the `{{time}}` which has a default value of `1000` so you can just press ENTER to accept the default value.
+And for `{{you}}` a value must be entered, and we entered `Jack` in this example.
+
+You may want to use this for Camel prototypes where you want the user to be able to enter custom values quickly.
+Those values can of course be pre-configured in `application.properties` as well.
 
 === Running Route from input parameter
 
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 0afec4ba0ae..a58d5c576cf 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -268,6 +268,10 @@ public class Run extends CamelCommand {
             description = "Whether to ignore route loading and compilation errors (use this with care!)")
     protected boolean ignoreLoadingError;
 
+    @Option(names = { "--prompt" },
+            description = "Allow user to type in required parameters in prompt if not present in application")
+    boolean prompt;
+
     public Run(CamelJBangMain main) {
         super(main);
     }
@@ -473,6 +477,9 @@ public class Run extends CamelCommand {
         if (ignoreLoadingError) {
             writeSetting(main, profileProperties, "camel.jbang.ignoreLoadingError", "true");
         }
+        if (prompt) {
+            writeSetting(main, profileProperties, "camel.jbang.prompt", "true");
+        }
         writeSetting(main, profileProperties, "camel.jbang.compileWorkDir", WORK_DIR + File.separator + "compile");
 
         if (gav != null) {
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 2f87e6c2ae4..10ccaae091f 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -59,6 +59,7 @@ import org.apache.camel.main.download.KnownDependenciesResolver;
 import org.apache.camel.main.download.KnownReposResolver;
 import org.apache.camel.main.download.MavenDependencyDownloader;
 import org.apache.camel.main.download.PackageNameSourceLoader;
+import org.apache.camel.main.download.PromptPropertyPlaceholderSource;
 import org.apache.camel.main.download.StubBeanRepository;
 import org.apache.camel.main.download.TypeConverterLoaderDownloadListener;
 import org.apache.camel.main.injection.AnnotationDependencyInjection;
@@ -357,6 +358,11 @@ public class KameletMain extends MainCommandLineSupport {
         // setup backlog recorder from very start
         answer.getCamelContextExtension().setStartupStepRecorder(new BacklogStartupStepRecorder());
 
+        boolean prompt = "true".equals(getInitialProperties().get("camel.jbang.prompt"));
+        if (prompt) {
+            answer.getPropertiesComponent().addPropertiesSource(new PromptPropertyPlaceholderSource());
+        }
+
         ClassLoader dynamicCL = createApplicationContextClassLoader(answer);
         answer.setApplicationContextClassLoader(dynamicCL);
         PluginHelper.getPackageScanClassResolver(answer).addClassLoader(dynamicCL);
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/PromptPropertyPlaceholderSource.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/PromptPropertyPlaceholderSource.java
new file mode 100644
index 00000000000..1ff11a89c4e
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/PromptPropertyPlaceholderSource.java
@@ -0,0 +1,55 @@
+/*
+ * 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.main.download;
+
+import org.apache.camel.Ordered;
+import org.apache.camel.spi.PropertiesSource;
+
+public class PromptPropertyPlaceholderSource implements PropertiesSource, Ordered {
+
+    @Override
+    public String getName() {
+        return "prompt";
+    }
+
+    @Override
+    public String getProperty(String name) {
+        return null; // not in use
+    }
+
+    @Override
+    public String getProperty(String name, String defaultValue) {
+        String answer;
+        if (defaultValue != null) {
+            answer = System.console().readLine("Enter optional value for %s (%s): ", name, defaultValue);
+        } else {
+            do {
+                answer = System.console().readLine("Enter required value for %s: ", name);
+            } while (answer == null || answer.isBlank());
+        }
+        // if user press enter then the value should use the default value
+        if (answer == null || answer.isBlank()) {
+            answer = defaultValue;
+        }
+        return answer;
+    }
+
+    @Override
+    public int getOrder() {
+        return Ordered.LOWEST;
+    }
+}