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/15 20:52:06 UTC

(camel) branch prompt created (now d87234e83fc)

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

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


      at d87234e83fc 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.

This branch includes the following new commits:

     new d87234e83fc 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.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: 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.

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d87234e83fc00fcdf7e098d104f8960ddf4177a8
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Dec 15 21:51:53 2023 +0100

    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..2db8aa7b617
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/PromptPropertyPlaceholderSource.java
@@ -0,0 +1,55 @@
+package org.apache.camel.main.download;
+
+import org.apache.camel.Ordered;
+import org.apache.camel.spi.PropertiesSource;
+
+/*
+ * 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.
+ */
+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;
+    }
+}