This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch se in repository https://gitbox.apache.org/repos/asf/camel.git
commit 20fb3158d9efb24bdd8b136af38a2df60897276e Author: Claus Ibsen <[email protected]> AuthorDate: Thu Feb 5 13:46:42 2026 +0100 CAMEL-22935: camel-jbang - Add eval expression command --- .../apache/camel/catalog/dev-consoles.properties | 1 + .../camel/catalog/dev-consoles/eval-language.json | 15 ++ .../impl/engine/DefaultSimpleFunctionRegistry.java | 3 +- .../apache/camel/dev-console/eval-language.json | 15 ++ .../org/apache/camel/dev-console/eval-language | 2 + .../org/apache/camel/dev-consoles.properties | 2 +- .../camel/impl/console/EvalLanguageDevConsole.java | 133 ++++++++++++ .../impl/console/SimpleLanguageDevConsole.java | 12 +- .../java/org/apache/camel/util/SimpleUtils.java | 4 +- .../pages/jbang-commands/camel-jbang-commands.adoc | 1 + .../camel-jbang-eval-expression.adoc | 32 +++ .../pages/jbang-commands/camel-jbang-eval.adoc | 34 ++++ .../camel-jbang-transform-message.adoc | 2 +- .../camel/cli/connector/LocalCliConnector.java | 27 +++ .../META-INF/camel-jbang-commands-metadata.json | 3 +- .../dsl/jbang/core/commands/CamelJBangMain.java | 2 + .../camel/dsl/jbang/core/commands/EvalCommand.java | 36 ++++ .../commands/action/EvalExpressionCommand.java | 224 +++++++++++++++++++++ .../commands/action/TransformMessageAction.java | 2 +- .../dsl/jbang/core/commands/EvalSimpleTest.java | 47 +++++ .../ParameterExceptionHandlerTest.java | 2 +- 21 files changed, 587 insertions(+), 12 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties index eccfcfb03b00..6954ed2b5d4e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties @@ -10,6 +10,7 @@ consumer context debug endpoint +eval-language event fault-tolerance gc diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/eval-language.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/eval-language.json new file mode 100644 index 000000000000..6082795c18bb --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/eval-language.json @@ -0,0 +1,15 @@ +{ + "console": { + "kind": "console", + "group": "camel", + "name": "eval-language", + "title": "Evaluate Language", + "description": "Evaluate Language and display result", + "deprecated": false, + "javaType": "org.apache.camel.impl.console.EvalLanguageDevConsole", + "groupId": "org.apache.camel", + "artifactId": "camel-console", + "version": "4.18.0-SNAPSHOT" + } +} + diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java index 143f8a110d6d..706d14c14521 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java @@ -19,6 +19,7 @@ package org.apache.camel.impl.engine; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import org.apache.camel.CamelContext; @@ -127,7 +128,7 @@ public class DefaultSimpleFunctionRegistry extends ServiceSupport implements Sim @Override public Set<String> getCustomFunctionNames() { - return functions.keySet(); + return new TreeSet<>(functions.keySet()); } @Override diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/eval-language.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/eval-language.json new file mode 100644 index 000000000000..6082795c18bb --- /dev/null +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/eval-language.json @@ -0,0 +1,15 @@ +{ + "console": { + "kind": "console", + "group": "camel", + "name": "eval-language", + "title": "Evaluate Language", + "description": "Evaluate Language and display result", + "deprecated": false, + "javaType": "org.apache.camel.impl.console.EvalLanguageDevConsole", + "groupId": "org.apache.camel", + "artifactId": "camel-console", + "version": "4.18.0-SNAPSHOT" + } +} + diff --git a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/eval-language b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/eval-language new file mode 100644 index 000000000000..19ba1cc8d860 --- /dev/null +++ b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/eval-language @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.impl.console.EvalLanguageDevConsole diff --git a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties index d1f4d2b43665..c5c4e618f264 100644 --- a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties +++ b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties @@ -1,5 +1,5 @@ # Generated by camel build tools - do NOT edit this file! -dev-consoles=bean blocked browse circuit-breaker consumer context debug endpoint event gc health inflight internal-tasks java-security jvm log memory message-history processor producer properties receive reload rest route route-controller route-dump route-group route-structure send service simple-language source startup-recorder system-properties thread top trace transformers type-converters variables +dev-consoles=bean blocked browse circuit-breaker consumer context debug endpoint eval-language event gc health inflight internal-tasks java-security jvm log memory message-history processor producer properties receive reload rest route route-controller route-dump route-group route-structure send service simple-language source startup-recorder system-properties thread top trace transformers type-converters variables groupId=org.apache.camel artifactId=camel-console version=4.18.0-SNAPSHOT diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/EvalLanguageDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/EvalLanguageDevConsole.java new file mode 100644 index 000000000000..0040eace7604 --- /dev/null +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/EvalLanguageDevConsole.java @@ -0,0 +1,133 @@ +/* + * 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.impl.console; + +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.Expression; +import org.apache.camel.Predicate; +import org.apache.camel.spi.annotations.DevConsole; +import org.apache.camel.support.DefaultExchange; +import org.apache.camel.support.MessageHelper; +import org.apache.camel.support.console.AbstractDevConsole; +import org.apache.camel.util.json.JsonObject; + +@DevConsole(name = "eval-language", displayName = "Evaluate Language", description = "Evaluate Language and display result") +public class EvalLanguageDevConsole extends AbstractDevConsole { + + /** + * The language to use + */ + public static final String LANGUAGE = "language"; + + /** + * Template to use for executing simple language function + */ + public static final String TEMPLATE = "template"; + + /** + * Whether to execute as predicate (use expression by default) + */ + public static final String PREDICATE = "predicate"; + + /** + * Optional message body + */ + public static final String BODY = "body"; + + /** + * Optional message headers + */ + public static final String HEADERS = "headers"; + + public EvalLanguageDevConsole() { + super("camel", "eval-language", "Evaluate Language", "Evaluate Language and display result"); + } + + @Override + protected String doCallText(Map<String, Object> options) { + StringBuilder sb = new StringBuilder(); + + String language = (String) options.getOrDefault(LANGUAGE, "simple"); + String template = (String) options.get(TEMPLATE); + if (template != null) { + Exchange dummy = new DefaultExchange(getCamelContext()); + dummy.getMessage().setBody(options.get(BODY)); + var headers = options.get(HEADERS); + if (headers instanceof Map map) { + dummy.getMessage().setHeaders(map); + } + + String out; + boolean predicate = options.getOrDefault(PREDICATE, "false").equals("true"); + if (predicate) { + Predicate pre = getCamelContext().resolveLanguage(language).createPredicate(template); + out = pre.matches(dummy) ? "true" : "false"; + } else { + Expression exp = getCamelContext().resolveLanguage(language).createExpression(template); + out = exp.evaluate(dummy, String.class); + } + sb.append(String.format("%nEvaluating (%s): %s", language, template)); + sb.append("\n"); + sb.append(out); + } + return sb.toString(); + } + + @Override + protected JsonObject doCallJson(Map<String, Object> options) { + JsonObject root = new JsonObject(); + + String language = (String) options.getOrDefault(LANGUAGE, "simple"); + String template = (String) options.get(TEMPLATE); + if (template != null) { + Exchange dummy = new DefaultExchange(getCamelContext()); + dummy.getMessage().setBody(options.get(BODY)); + var headers = options.get(HEADERS); + if (headers instanceof Map map) { + dummy.getMessage().setHeaders(map); + } + + Exception cause = null; + String out = null; + try { + boolean predicate = options.getOrDefault(PREDICATE, "false").equals("true"); + if (predicate) { + Predicate pre = getCamelContext().resolveLanguage(language).createPredicate(template); + out = pre.matches(dummy) ? "true" : "false"; + } else { + Expression exp = getCamelContext().resolveLanguage(language).createExpression(template); + out = exp.evaluate(dummy, String.class); + } + } catch (Exception e) { + cause = e; + } + + if (cause != null) { + root.put("status", "failed"); + root.put("exception", + MessageHelper.dumpExceptionAsJSonObject(cause).getMap("exception")); + } else { + root.put("status", "success"); + root.put("result", out); + } + } + + return root; + } +} diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java index 517c1eaea2fb..4577a5e54d98 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java @@ -39,20 +39,23 @@ public class SimpleLanguageDevConsole extends AbstractDevConsole { SimpleFunctionRegistry reg = PluginHelper.getSimpleFunctionRegistry(getCamelContext()); sb.append(String.format("%n Core Functions: %d", reg.coreSize())); for (String name : reg.getCoreFunctionNames()) { - sb.append(String.format("%n %s", name)); + sb.append(String.format("%n %s", name)); } + sb.append("\n"); sb.append(String.format("%n Custom Functions: %d", reg.customSize())); for (String name : reg.getCustomFunctionNames()) { - sb.append(String.format("%n %s", name)); + sb.append(String.format("%n %s", name)); } + sb.append("\n"); + return sb.toString(); } @Override protected JsonObject doCallJson(Map<String, Object> options) { - SimpleFunctionRegistry reg = PluginHelper.getSimpleFunctionRegistry(getCamelContext()); - JsonObject root = new JsonObject(); + + SimpleFunctionRegistry reg = PluginHelper.getSimpleFunctionRegistry(getCamelContext()); root.put("coreSize", reg.coreSize()); root.put("customSize", reg.customSize()); JsonArray arr = new JsonArray(); @@ -65,6 +68,7 @@ public class SimpleLanguageDevConsole extends AbstractDevConsole { if (!arr.isEmpty()) { root.put("customFunctions", arr); } + return root; } } diff --git a/core/camel-util/src/main/java/org/apache/camel/util/SimpleUtils.java b/core/camel-util/src/main/java/org/apache/camel/util/SimpleUtils.java index 7319c067c4ed..b55cba73b9d4 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/SimpleUtils.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/SimpleUtils.java @@ -18,12 +18,12 @@ package org.apache.camel.util; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; public class SimpleUtils { - private static final Set<String> SIMPLE_FUNCTIONS = Collections.unmodifiableSet(new HashSet<>( + private static final Set<String> SIMPLE_FUNCTIONS = Collections.unmodifiableSet(new LinkedHashSet<>( Arrays.asList( // Generated by camel build tools - do NOT edit this list! // SIMPLE-FUNCTIONS: START diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc index d519eb557fe6..5f0c3ac8c98e 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc @@ -20,6 +20,7 @@ TIP: You can also use `camel --help` or `camel <command> --help` to see availabl | xref:jbang-commands/camel-jbang-dependency.adoc[camel dependency] | Displays all Camel dependencies required to run | xref:jbang-commands/camel-jbang-dirty.adoc[camel dirty] | Check if there are dirty files from previous Camel runs that did not terminate gracefully | xref:jbang-commands/camel-jbang-doc.adoc[camel doc] | Shows documentation for kamelet, component, and other Camel resources +| xref:jbang-commands/camel-jbang-eval.adoc[camel eval] | Evaluate Camel expressions and scripts | xref:jbang-commands/camel-jbang-explain.adoc[camel explain] | Explain what a Camel route does using AI/LLM | xref:jbang-commands/camel-jbang-export.adoc[camel export] | Export to other runtimes (Camel Main, Spring Boot, or Quarkus) | xref:jbang-commands/camel-jbang-get.adoc[camel get] | Get status of Camel integrations diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval-expression.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval-expression.adoc new file mode 100644 index 000000000000..ea2b9e869141 --- /dev/null +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval-expression.adoc @@ -0,0 +1,32 @@ + +// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE += camel eval expression + +Evaluates Camel expression + + +== Usage + +[source,bash] +---- +camel eval expression [options] +---- + + + +== Options + +[cols="2,5,1,2",options="header"] +|=== +| Option | Description | Default | Type +| `--body` | Message body (prefix with file: to refer to loading message body from file) | | String +| `--camel-version` | To run using a different Camel version than the default version. | | String +| `--header` | Message header (key=value) | | List +| `--repo,--repos` | Additional maven repositories (Use commas to separate multiple repositories) | | String +| `--template` | The template to use for evaluating (prefix with file: to refer to loading template from file) | | String +| `--timeout` | Timeout in millis waiting for evaluation to be done | 10000 | long +| `--watch` | Execute periodically and showing output fullscreen | | boolean +| `-h,--help` | Display the help and sub-commands | | boolean +|=== + + diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval.adoc new file mode 100644 index 000000000000..20ca09b59751 --- /dev/null +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-eval.adoc @@ -0,0 +1,34 @@ + +// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE += camel eval + +Evaluate Camel expressions and scripts + + +== Usage + +[source,bash] +---- +camel eval [options] +---- + + +== Subcommands + +[cols="2,5",options="header"] +|=== +| Subcommand | Description +| xref:jbang-commands/camel-jbang-eval-expression.adoc[expression] | Evaluates Camel expression +|=== + + + +== Options + +[cols="2,5,1,2",options="header"] +|=== +| Option | Description | Default | Type +| `-h,--help` | Display the help and sub-commands | | boolean +|=== + + diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-transform-message.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-transform-message.adoc index 2e50c308475e..68d27963cb00 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-transform-message.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-transform-message.adoc @@ -35,7 +35,7 @@ camel transform message [options] | `--show-exchange-properties` | Show exchange properties from the output message | false | boolean | `--show-headers` | Show message headers from the output message | true | boolean | `--source` | Instead of using external template file then refer to an existing Camel route source with inlined Camel language expression in a route. (use :line-number or :id to refer to the exact location of the EIP to use) | | String -| `--template` | The template to use for message transformation (prefix with file: to refer to loading message body from file) | | String +| `--template` | The template to use for message transformation (prefix with file: to refer to loading template from file) | | String | `--timeout` | Timeout in millis waiting for message to be transformed | 20000 | long | `--watch` | Execute periodically and showing output fullscreen | | boolean | `-h,--help` | Display the help and sub-commands | | boolean diff --git a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java index b12e69a6f7c7..eaf6e37df925 100644 --- a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java +++ b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java @@ -292,6 +292,8 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C doActionLoggerTask(root); } else if ("gc".equals(action)) { System.gc(); + } else if ("eval".equals(action)) { + doActionEvalTask(root); } else if ("load".equals(action)) { doActionLoadTask(root); } else if ("reload".equals(action)) { @@ -856,6 +858,31 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C } } + private void doActionEvalTask(JsonObject root) throws Exception { + DevConsole dc = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class) + .resolveById("eval-language"); + if (dc != null) { + String lan = root.getStringOrDefault("language", "simple"); + boolean predicate = root.getBooleanOrDefault("predicate", false); + String template = root.getStringOrDefault("template", ""); + String body = root.getStringOrDefault("body", ""); + Map<String, String> map = new LinkedHashMap<>(); + Collection<JsonObject> headers = root.getCollection("headers"); + if (headers != null) { + map = new LinkedHashMap<>(); + for (JsonObject jo : headers) { + map.put(jo.getString("key"), jo.getString("value")); + } + } + JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON, + Map.of("language", lan, "predicate", predicate, "template", template, "body", body, "headers", map)); + LOG.trace("Updating output file: {}", outputFile); + IOHelper.writeText(json.toJson(), outputFile); + } else { + IOHelper.writeText("{}", outputFile); + } + } + private void doActionLoadTask(JsonObject root) throws Exception { List<String> files = root.getCollection("source"); boolean restart = root.getBooleanOrDefault("restart", false); diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json index bc094d14443a..ff00446a0317 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json @@ -9,6 +9,7 @@ { "name": "dependency", "fullName": "dependency", "description": "Displays all Camel dependencies required to run", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "copy", "fullName": "dependency copy", "description": "Copies all Camel dependencies required to run to a specific directory", "sourc [...] { "name": "dirty", "fullName": "dirty", "description": "Check if there are dirty files from previous Camel runs that did not terminate gracefully", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty", "options": [ { "names": "--clean", "description": "Clean dirty files which are no longer in use", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", " [...] { "name": "doc", "fullName": "doc", "description": "Shows documentation for kamelet, component, and other Camel resources", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ { "names": "--camel-version", "description": "To use a different Camel version than the default version", "javaType": "java.lang.String", "type": "string" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet [...] + { "name": "eval", "fullName": "eval", "description": "Evaluate Camel expressions and scripts", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", "fullName": "eval expression", "description": "Evaluates Camel expression", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...] { "name": "explain", "fullName": "explain", "description": "Explain what a Camel route does using AI\/LLM", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": "--api-key", "description": "API key for authentication. Also reads OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", "type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", "javaTyp [...] { "name": "export", "fullName": "export", "description": "Export to other runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": "--build-property", "description": "Maven\/Gradle build properties, ex. --build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { "names": "--build-tool", "description": "DEPRECATED: Build tool to use (maven or gradle) (gradle is deprecated)", "defaultV [...] { "name": "get", "fullName": "get", "description": "Get status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", "fullName": "get [...] @@ -27,7 +28,7 @@ { "name": "stop", "fullName": "stop", "description": "Shuts down running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.StopProcess", "options": [ { "names": "--kill", "description": "To force killing the process (SIGKILL)", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "top", "fullName": "top", "description": "Top status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelTop", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "context", "fullName": "top [...] { "name": "trace", "fullName": "trace", "description": "Tail message traces from running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction", "options": [ { "names": "--action", "description": "Action to start, stop, clear, list status, or dump traces", "defaultValue": "status", "javaType": "java.lang.String", "type": "string" }, { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "b [...] - { "name": "transform", "fullName": "transform", "description": "Transform message or Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "message", "fullName": "transform message", "description": "Transform message from one format to another via an existing running Camel integration", " [...] + { "name": "transform", "fullName": "transform", "description": "Transform message or Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "message", "fullName": "transform message", "description": "Transform message from one format to another via an existing running Camel integration", " [...] { "name": "update", "fullName": "update", "description": "Update Camel project", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "list", "fullName": "update list", "description": "List available update versions for Camel and its runtime variants", "sourceClass": "org.apache.camel.dsl.jbang.cor [...] { "name": "version", "fullName": "version", "description": "Manage Camel versions", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "version get", "description": "Displays current Camel version", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionGet", [...] ] diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java index 885d8d490dfd..d959b8267e17 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java @@ -125,6 +125,8 @@ public class CamelJBangMain implements Callable<Integer> { .addSubcommand("runtime", new CommandLine(new DependencyRuntime(main))) .addSubcommand("update", new CommandLine(new DependencyUpdate(main)))) .addSubcommand("dirty", new CommandLine(new Dirty(main))) + .addSubcommand("eval", new CommandLine(new EvalCommand(main)) + .addSubcommand("expression", new CommandLine(new EvalExpressionCommand(main)))) .addSubcommand("export", new CommandLine(new Export(main))) .addSubcommand("explain", new CommandLine(new Explain(main))) .addSubcommand("get", new CommandLine(new CamelStatus(main)) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/EvalCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/EvalCommand.java new file mode 100644 index 000000000000..3af4549c48d4 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/EvalCommand.java @@ -0,0 +1,36 @@ +/* + * 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.dsl.jbang.core.commands; + +import picocli.CommandLine; + [email protected](name = "eval", + description = "Evaluate Camel expressions and scripts (use transform --help to see sub commands)", + sortOptions = false, showDefaultValues = true) +public class EvalCommand extends CamelCommand { + + public EvalCommand(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + new CommandLine(this).execute("--help"); + return 0; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/EvalExpressionCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/EvalExpressionCommand.java new file mode 100644 index 000000000000..bd4815d27144 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/EvalExpressionCommand.java @@ -0,0 +1,224 @@ +/* + * 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.dsl.jbang.core.commands.action; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.commands.Run; +import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; +import org.apache.camel.dsl.jbang.core.common.PathUtils; +import org.apache.camel.dsl.jbang.core.common.VersionHelper; +import org.apache.camel.util.StopWatch; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; +import org.fusesource.jansi.Ansi; +import picocli.CommandLine; + [email protected](name = "expression", + description = "Evaluates Camel expression", sortOptions = false, + showDefaultValues = true) +public class EvalExpressionCommand extends ActionWatchCommand { + + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1") + String name = "*"; + + @CommandLine.Option(names = { "--camel-version" }, + description = "To run using a different Camel version than the default version.") + String camelVersion; + + @CommandLine.Parameters(description = "Language to use", defaultValue = "simple") + String language; + + @CommandLine.Parameters(description = "Whether to force evaluating as predicate", defaultValue = "false") + boolean predicate; + + @CommandLine.Option(names = { + "--template" }, + description = "The template to use for evaluating (prefix with file: to refer to loading template from file)", + required = true) + private String template; + + @CommandLine.Option(names = { "--body" }, + description = "Message body (prefix with file: to refer to loading message body from file)") + String body; + + @CommandLine.Option(names = { "--header" }, + description = "Message header (key=value)") + List<String> headers; + + @CommandLine.Option(names = { "--timeout" }, defaultValue = "10000", + description = "Timeout in millis waiting for evaluation to be done") + long timeout = 10000; + + @CommandLine.Option(names = { "--repo", "--repos" }, + description = "Additional maven repositories (Use commas to separate multiple repositories)") + String repositories; + + long pid; + + public EvalExpressionCommand(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + if (template == null || template.isBlank()) { + printer().printErr("Template is required"); + return -1; + } + if (template.startsWith("file:")) { + Path f = Path.of(template.substring(5)); + if (!Files.exists(f)) { + printer().printErr("Template file does not exist: " + f); + return -1; + } + } + + Integer exit; + List<Long> pids = findPids(name); + if (pids.size() == 1) { + this.pid = pids.get(0); + printer().println("Using existing running Camel integration to evaluate (pid: " + this.pid + ")"); + exit = super.doCall(); + } else { + try { + // start a new empty camel in the background + Run run = new Run(getMain()); + // requires camel 4.3 onwards + if (camelVersion != null && VersionHelper.isLE(camelVersion, "4.2.0")) { + printer().printErr("This requires Camel version 4.3 or newer"); + return -1; + } + exit = run.runTransformMessage(camelVersion, repositories); + this.pid = run.spawnPid; + if (exit == 0) { + exit = super.doCall(); + } + } finally { + if (pid > 0) { + // cleanup output file + Path outputFile = getOutputFile(Long.toString(pid)); + PathUtils.deleteFile(outputFile); + // stop running camel as we are done + Path parent = CommandLineHelper.getCamelDir(); + Path pidFile = parent.resolve(Long.toString(pid)); + if (Files.exists(pidFile)) { + PathUtils.deleteFile(pidFile); + } + } + } + } + + return exit; + } + + @Override + protected Integer doWatchCall() throws Exception { + JsonObject root = new JsonObject(); + root.put("action", "eval"); + root.put("language", language); + root.put("predicate", predicate); + root.put("template", Jsoner.escape(template)); + if (body != null) { + root.put("body", Jsoner.escape(body)); + } + if (headers != null) { + JsonArray arr = new JsonArray(); + for (String h : headers) { + JsonObject jo = new JsonObject(); + if (!h.contains("=")) { + printer().println("Header must be in key=value format, was: " + h); + return 0; + } + jo.put("key", StringHelper.before(h, "=")); + jo.put("value", StringHelper.after(h, "=")); + arr.add(jo); + } + root.put("headers", arr); + } + + Path outputFile = getOutputFile(Long.toString(pid)); + PathUtils.deleteFile(outputFile); + + Path f = getActionFile(Long.toString(pid)); + try { + PathUtils.writeTextSafely(root.toJson(), f); + } catch (Exception e) { + // ignore + } + + JsonObject jo = waitForOutputFile(outputFile); + if (jo != null) { + String status = jo.getString("status"); + if ("success".equals(status)) { + String result = jo.getString("result"); + printer().println(result); + } else { + JsonObject cause = jo.getMap("exception"); + if (cause != null) { + String msg = cause.getString("message"); + if (msg != null) { + msg = Jsoner.unescape(msg); + } + String st = cause.getString("stackTrace"); + if (st != null) { + st = Jsoner.unescape(st); + } + if (msg != null) { + String text = Ansi.ansi().fgRed().a(msg).reset().toString(); + printer().printErr(text); + printer().println(); + } + if (st != null) { + String text = Ansi.ansi().fgRed().a(st).reset().toString(); + printer().printErr(text); + printer().println(); + } + return 1; + } + } + } + + return 0; + } + + protected JsonObject waitForOutputFile(Path outputFile) { + StopWatch watch = new StopWatch(); + while (watch.taken() < timeout) { + try { + // give time for response to be ready + Thread.sleep(20); + + if (Files.exists(outputFile)) { + String text = Files.readString(outputFile); + return (JsonObject) Jsoner.deserialize(text); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + // ignore + } + } + return null; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/TransformMessageAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/TransformMessageAction.java index 274af9d325c2..081fcbf4dec3 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/TransformMessageAction.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/TransformMessageAction.java @@ -76,7 +76,7 @@ public class TransformMessageAction extends ActionWatchCommand { @CommandLine.Option(names = { "--template" }, - description = "The template to use for message transformation (prefix with file: to refer to loading message body from file)") + description = "The template to use for message transformation (prefix with file: to refer to loading template from file)") private String template; @CommandLine.Option(names = { "--option" }, diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/EvalSimpleTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/EvalSimpleTest.java new file mode 100644 index 000000000000..0e9554553cd3 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/EvalSimpleTest.java @@ -0,0 +1,47 @@ +/* + * 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.dsl.jbang.core.commands; + +import org.apache.camel.dsl.jbang.core.commands.action.EvalExpressionCommand; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; + +class EvalSimpleTest extends CamelCommandBaseTestSupport { + + @Test + public void shouldEvalSimple() throws Exception { + String[] args = new String[] { "--template=${length()}", "--body=hello_world" }; + EvalExpressionCommand command = createCommand(args); + int exit = command.doCall(); + Assertions.assertEquals(0, exit); + + var lines = printer.getLines(); + Assertions.assertNotNull(lines); + Assertions.assertEquals(2, lines.size()); + Assertions.assertEquals("11", lines.get(1)); + } + + private EvalExpressionCommand createCommand(String... args) { + EvalExpressionCommand command = new EvalExpressionCommand(new CamelJBangMain().withPrinter(printer)); + if (args != null) { + CommandLine.populateCommand(command, args); + } + return command; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/exceptionhandler/ParameterExceptionHandlerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/exceptionhandler/ParameterExceptionHandlerTest.java index 83490a515a7b..0155c7ef70f2 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/exceptionhandler/ParameterExceptionHandlerTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/exceptionhandler/ParameterExceptionHandlerTest.java @@ -34,7 +34,7 @@ class ParameterExceptionHandlerTest { Assertions.assertEquals(5, lines.length, "5 lines for the error is expected but received " + lines.length); Assertions.assertEquals("Unmatched argument at index 0: 'firstInvalid'", lines[0], "First line mentioning unmatched argument"); - Assertions.assertEquals("Did you mean: camel stop or camel infra or camel plugin?", lines[1], + Assertions.assertEquals("Did you mean: camel eval or camel stop or camel infra?", lines[1], "Second line with suggestion in case it is a typo"); Assertions.assertEquals( "Maybe a specific Camel JBang plugin must be installed? (Try camel plugin --help' for more information)",
