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 <claus.ib...@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; + } +}