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

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

commit c257d9cc4308116c50aaee497c69a0afb6062624
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 27 09:59:15 2023 +0100

    CAMEL-20153: camel-jbang - Transform message command
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_3.adoc     |  44 ++--
 .../modules/ROOT/pages/camel-jbang.adoc            | 124 +++++++++
 .../camel/cli/connector/LocalCliConnector.java     |  71 +++++
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   5 +-
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  73 +++++-
 .../dsl/jbang/core/commands/TransformCommand.java  |  35 +++
 .../{Transform.java => TransformRoute.java}        |  10 +-
 .../commands/action/TransformMessageAction.java    | 289 +++++++++++++++++++++
 8 files changed, 615 insertions(+), 36 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_3.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_3.adoc
index 1eff390ac1a..29e4cb334bf 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_3.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_3.adoc
@@ -6,21 +6,7 @@ from both 4.0 to 4.1 and 4.1 to 4.2.
 
 == Upgrading Camel 4.2 to 4.3
 
-=== camel-kafka
-
-The behavior for `breakOnFirstError` was altered as numerous issues were 
fixed. The behavior related to committing 
-the offset is now determined by the `CommitManager` that is configured. 
-
-When the default `CommitManager` is used (`NoopCommitManager`) then no commit 
is performed. The route implementation will
-be responsible for managing the offset using `KafkaManualCommit` to manage the 
retrying of the payload.
-
-When using the `SyncCommitManager` then the offset will be committed so that 
the payload is continually retried. This was
-the behavior described in the documentation.
-
-When using the `AsyncCommitManager` then the offset will be committed so that 
the payload is continually retried. This was
-the behavior described in the documentation.
-
-=== throttle
+=== Throttle EIP
 
 Throttle now uses the number of concurrent requests as the throttling measure 
instead of the number of requests
 per period.
@@ -30,9 +16,10 @@ and remove any `timePeriodMillis` option.
 
 For example, update the following:
 
-[source]
+[source,java]
 ----
 long maxRequestsPerPeriod = 100L;
+
 from("seda:a")
   .throttle(maxRequestsPerPeriod).timePeriodMillis(500)
   .to("seda:b")
@@ -45,9 +32,10 @@ from("seda:c")
 
 to use `maxConcurrentRequests`:
 
-[source]
+[source,java]
 ----
 long maxConcurrentRequests = 30L;
+
 from("seda:a")
   .throttle(maxConcurrentRequests)
   .to("seda:b")
@@ -56,3 +44,25 @@ from("seda:c")
   .throttle(maxConcurrentRequests)
   .to("seda:d")
 ----
+
+=== camel-jbang
+
+The `camel transform` command has been renamed to `camel transform route` as 
this command is used for transforming
+routes between DSLs such as XML to YAML.
+
+There is a new `camel transform message` command to do message transformation.
+
+=== camel-kafka
+
+The behavior for `breakOnFirstError` was altered as numerous issues were 
fixed. The behavior related to committing 
+the offset is now determined by the `CommitManager` that is configured. 
+
+When the default `CommitManager` is used (`NoopCommitManager`) then no commit 
is performed. The route implementation will
+be responsible for managing the offset using `KafkaManualCommit` to manage the 
retrying of the payload.
+
+When using the `SyncCommitManager` then the offset will be committed so that 
the payload is continually retried. This was
+the behavior described in the documentation.
+
+When using the `AsyncCommitManager` then the offset will be committed so that 
the payload is continually retried. This was
+the behavior described in the documentation.
+
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index dc4ac941fd5..a4b7e769ddb 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -2121,6 +2121,130 @@ which is due to invalid configuration: `Invalid url in 
bootstrap.servers: value`
 
 TIP: Use `camel get health --help` to see all the various options.
 
+== Transforming message (data mapping)
+
+When integrating system you often need to transform messages from one system 
to another. Camel has rich set
+of capabilities for this such as various data formats, templating languages, 
and much more. However, for basic
+data mapping such as taking an existing incoming JSon document and transform 
this to a smaller JSon document,
+you may want to do this quickly with Camel.
+
+The `camel transform message` command can be used for such tasks, where it can 
take an existing source file as input,
+and then a template that defines how to transform the data, and then show the 
output (in real time).
+
+For example given this JSon document (in a file named `random.json`), we want 
to combine the name, and select a few fields:
+
+[source,json]
+----
+{
+  "id": 9914,
+  "uid": "eb5fa603-1db6-45f9-912a-431a6ed59b18",
+  "password": "ei7gvYKdnN",
+  "first_name": "Khalilah",
+  "last_name": "Monahan",
+  "username": "khalilah.monahan",
+  "email": "[email protected]",
+  "avatar": 
"https://robohash.org/utnumquamexcepturi.png?size=300x300&set=set1";,
+  "gender": "Agender",
+  "phone_number": "+54 (421) 591-5640 x333",
+  "social_insurance_number": "268418308",
+  "date_of_birth": "1975-03-11",
+  "employment": {
+    "title": "Product Design Director",
+    "key_skill": "Work under pressure"
+  },
+  "address": {
+    "city": "New Fritzchester",
+    "street_name": "Patrick Common",
+    "street_address": "4538 Reggie Inlet",
+    "zip_code": "16282-7045",
+    "state": "New York",
+    "country": "United States",
+    "coordinates": {
+      "lat": -1.9868753435474673,
+      "lng": 39.09763956726292
+    }
+  },
+  "credit_card": {
+    "cc_number": "4493983042212"
+  },
+  "subscription": {
+    "plan": "Student",
+    "status": "Active",
+    "payment_method": "Debit card",
+    "term": "Monthly"
+  }
+}
+----
+
+Then we can have a `transform.json` file as the beginning of the template, 
with the structure of the desired output:
+
+[source,json]
+----
+{
+    "sid": 123,
+       "name": "TODO",
+       "country": "TODO",
+       "phone": "TODO",
+       "student": false
+}
+----
+
+We can then run `camel transform message` and have it update (in real time) 
the output every time we change the template.
+
+[source,bash]
+----
+$ camel transform message --body=file:random.json --language=simple 
--template=file:transform.json --pretty --watch
+----
+
+What happens is then Camel will output on the console as you go:
+
+[source,bash]
+----
+ Exchange  (DefaultExchange)  InOut   23F5DD4CE6C260B-0000000000000002
+ Message   (DefaultMessage)
+ Body      (String) (bytes: 118)
+ {
+   "sid": 123,
+   "name": "TODO",
+   "country": "TODO",
+   "phone": "TODO",
+   "student": false
+ }
+----
+
+Then you can update the `transform.json` file and save it and see the 
generated output:
+
+[source,json]
+----
+{
+   "sid": ${jq(.id)},
+   "name": "${jq(.first_name)} ${jq(.last_name)}",
+   "country": "TODO",
+   "phone": "TODO",
+   "student": false
+}
+----
+
+And the output:
+
+[source,bash]
+----
+ Exchange  (DefaultExchange)  InOut   23F5DD4CE6C260B-0000000000000018
+ Message   (DefaultMessage)
+ Body      (String) (bytes: 158)
+ {
+   "sid": 9914,
+   "name": "Khalilah Monahan",
+   "country": "TODO",
+   "phone": "TODO",
+   "student": false
+ }
+----
+
+Then you can continue to update the `transform.json` until you have the 
desired result. And if you make a mistake
+then you see an error (in red) with stacktrace that hopefully can help you out 
how to fix this.
+
+
 == Listing what Camel components is available
 
 Camel comes with a lot of artifacts out of the box which comes as:
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 e85ccfd8380..641569e55b9 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
@@ -27,6 +27,7 @@ import java.lang.management.RuntimeMXBean;
 import java.lang.management.ThreadMXBean;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -42,6 +43,7 @@ import org.apache.camel.CamelContextAware;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
+import org.apache.camel.Expression;
 import org.apache.camel.NoSuchEndpointException;
 import org.apache.camel.Processor;
 import org.apache.camel.ProducerTemplate;
@@ -52,6 +54,7 @@ import org.apache.camel.console.DevConsoleRegistry;
 import org.apache.camel.spi.CliConnector;
 import org.apache.camel.spi.CliConnectorFactory;
 import org.apache.camel.spi.ContextReloadStrategy;
+import org.apache.camel.spi.Language;
 import org.apache.camel.spi.ResourceReloadStrategy;
 import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.MessageHelper;
@@ -486,6 +489,74 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                         IOHelper.writeText(jo.toJson(), outputFile);
                     }
                 }
+            } else if ("transform".equals(action)) {
+                StopWatch watch = new StopWatch();
+                long timestamp = System.currentTimeMillis();
+                String language = root.getString("language");
+                String template = Jsoner.unescape(root.getString("template"));
+                if (template.startsWith("file:")) {
+                    template = "resource:" + template;
+                }
+                String body = Jsoner.unescape(root.getString("body"));
+                InputStream is = null;
+                Object b = body;
+                Map<String, Object> map = null;
+                if (body.startsWith("file:")) {
+                    File file = new File(body.substring(5));
+                    is = new FileInputStream(file);
+                    b = IOHelper.loadText(is);
+                }
+                Collection<JsonObject> headers = root.getCollection("headers");
+                if (headers != null) {
+                    map = new LinkedHashMap<>();
+                    for (JsonObject jo : headers) {
+                        map.put(jo.getString("key"), jo.getString("value"));
+                    }
+                }
+                final Object inputBody = b;
+                final Map<String, Object> inputHeaders = map;
+                Exchange out = 
camelContext.getCamelContextExtension().getExchangeFactory().create(false);
+                try {
+                    Language lan = camelContext.resolveLanguage(language);
+                    Expression exp = lan.createExpression(template);
+                    exp.init(camelContext);
+                    // create dummy exchange with
+                    out.setPattern(ExchangePattern.InOut);
+                    out.getMessage().setBody(inputBody);
+                    if (inputHeaders != null) {
+                        out.getMessage().setHeaders(inputHeaders);
+                    }
+                    String result = exp.evaluate(out, String.class);
+                    out.getMessage().setBody(result);
+                    IOHelper.close(is);
+                } catch (Exception e) {
+                    out.setException(e);
+                }
+                LOG.trace("Updating output file: {}", outputFile);
+                if (out.getException() != null) {
+                    JsonObject jo = new JsonObject();
+                    jo.put("language", language);
+                    jo.put("exchangeId", out.getExchangeId());
+                    jo.put("timestamp", timestamp);
+                    jo.put("elapsed", watch.taken());
+                    jo.put("status", "failed");
+                    // avoid double wrap
+                    jo.put("exception",
+                            
MessageHelper.dumpExceptionAsJSonObject(out.getException()).getMap("exception"));
+                    IOHelper.writeText(jo.toJson(), outputFile);
+                } else {
+                    JsonObject jo = new JsonObject();
+                    jo.put("language", language);
+                    jo.put("exchangeId", out.getExchangeId());
+                    jo.put("timestamp", timestamp);
+                    jo.put("elapsed", watch.taken());
+                    jo.put("status", "success");
+                    // avoid double wrap
+                    jo.put("message", 
MessageHelper.dumpAsJSonObject(out.getMessage(), true, true, true, true, true,
+                            BODY_MAX_CHARS).getMap("message"));
+                    IOHelper.writeText(jo.toJson(), outputFile);
+                }
+                
camelContext.getCamelContextExtension().getExchangeFactory().release(out);
             }
 
             // action done so delete file
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 dd9993ad45e..836773601c4 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
@@ -37,6 +37,7 @@ import 
org.apache.camel.dsl.jbang.core.commands.action.CamelThreadDump;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction;
 import org.apache.camel.dsl.jbang.core.commands.action.LoggerAction;
 import org.apache.camel.dsl.jbang.core.commands.action.RouteControllerAction;
+import org.apache.camel.dsl.jbang.core.commands.action.TransformMessageAction;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogComponent;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDataFormat;
@@ -93,7 +94,9 @@ public class CamelJBangMain implements Callable<Integer> {
                 .addSubcommand("ps", new CommandLine(new ListProcess(main)))
                 .addSubcommand("stop", new CommandLine(new StopProcess(main)))
                 .addSubcommand("trace", new CommandLine(new 
CamelTraceAction(main)))
-                .addSubcommand("transform", new CommandLine(new 
Transform(main)))
+                .addSubcommand("transform", new CommandLine(new 
TransformCommand(main))
+                        .addSubcommand("route", new CommandLine(new 
TransformRoute(main)))
+                        .addSubcommand("message", new CommandLine(new 
TransformMessageAction(main))))
                 .addSubcommand("get", new CommandLine(new CamelStatus(main))
                         .addSubcommand("context", new CommandLine(new 
CamelContextStatus(main)))
                         .addSubcommand("route", new CommandLine(new 
CamelRouteStatus(main)))
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 3df54e87611..e3d76d841f1 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
@@ -110,13 +110,14 @@ public class Run extends CamelCommand {
     private static final Pattern CLASS_PATTERN = Pattern.compile(
             "^\\s*public class\\s+([a-zA-Z0-9]*)[\\s+|;].*$", 
Pattern.MULTILINE);
 
-    boolean silentRun;
+    public boolean silentRun;
     boolean scriptRun;
     boolean transformRun;
+    boolean transformMessageRun;
     boolean debugRun;
 
     private File logFile;
-    long spawnPid;
+    public long spawnPid;
 
     @Parameters(description = "The Camel file(s) to run. If no files specified 
then application.properties is used as source for which files to run.",
                 arity = "0..9", paramLabel = "<files>", parameterConsumer = 
FilesConsumer.class)
@@ -129,7 +130,10 @@ public class Run extends CamelCommand {
     String sourceDir;
 
     @Option(names = { "--background" }, defaultValue = "false", description = 
"Run in the background")
-    boolean background;
+    public boolean background;
+
+    @Option(names = { "--empty" }, defaultValue = "false", description = "Run 
an empty Camel without loading source files")
+    public boolean empty;
 
     @Option(names = { "--camel-version" }, description = "To run using a 
different Camel version than the default version.")
     String camelVersion;
@@ -286,7 +290,7 @@ public class Run extends CamelCommand {
         return run();
     }
 
-    protected Integer runSilent() throws Exception {
+    public Integer runSilent() throws Exception {
         return runSilent(false);
     }
 
@@ -303,6 +307,16 @@ public class Run extends CamelCommand {
         return run();
     }
 
+    public Integer runTransformMessage(String camelVersion) throws Exception {
+        // just boot silently an empty camel in the background and exit
+        this.transformMessageRun = true;
+        this.background = true;
+        this.camelVersion = camelVersion;
+        this.empty = true;
+        this.name = "transform";
+        return run();
+    }
+
     protected Integer runScript(String file) throws Exception {
         this.files.add(file);
         this.scriptRun = true;
@@ -358,7 +372,7 @@ public class Run extends CamelCommand {
     }
 
     private int run() throws Exception {
-        if (!files.isEmpty() && sourceDir != null) {
+        if (!empty && !files.isEmpty() && sourceDir != null) {
             // cannot have both files and source dir at the same time
             System.err.println("Cannot specify both file(s) and source-dir at 
the same time.");
             return 1;
@@ -368,14 +382,14 @@ public class Run extends CamelCommand {
         removeDir(work);
         work.mkdirs();
 
-        Properties profileProperties = loadProfileProperties();
+        Properties profileProperties = !empty ? loadProfileProperties() : null;
         configureLogging();
         if (openapi != null) {
             generateOpenApi();
         }
 
         // route code as option
-        if (code != null) {
+        if (!empty && code != null) {
             // store code in temporary file
             String codeFile = loadFromCode(code);
             // use code as first file
@@ -383,7 +397,7 @@ public class Run extends CamelCommand {
         }
 
         // if no specific file to run then try to auto-detect
-        if (files.isEmpty() && sourceDir == null) {
+        if (!empty && files.isEmpty() && sourceDir == null) {
             String routes = profileProperties != null ? 
profileProperties.getProperty("camel.main.routesIncludePattern") : null;
             if (routes == null) {
                 if (!silentRun) {
@@ -501,6 +515,9 @@ public class Run extends CamelCommand {
             // do not run for very long in silent run
             main.addInitialProperty("camel.main.autoStartup", "false");
             main.addInitialProperty("camel.main.durationMaxSeconds", "1");
+        } else if (transformMessageRun) {
+            // do not start any routes
+            main.addInitialProperty("camel.main.autoStartup", "false");
         } else if (scriptRun) {
             // auto terminate if being idle
             main.addInitialProperty("camel.main.durationMaxIdleSeconds", "1");
@@ -800,6 +817,11 @@ public class Run extends CamelCommand {
     private Properties loadProfileProperties() throws Exception {
         Properties answer = null;
 
+        if (transformMessageRun) {
+            // do not load profile in transform message run as it should be 
vanilla empty
+            return answer;
+        }
+
         File profilePropertiesFile;
         if (sourceDir != null) {
             profilePropertiesFile = new File(sourceDir, getProfile() + 
".properties");
@@ -861,7 +883,16 @@ public class Run extends CamelCommand {
     }
 
     protected int runCamelVersion(KameletMain main) throws Exception {
-        List<String> cmds = new 
ArrayList<>(spec.commandLine().getParseResult().originalArgs());
+        List<String> cmds;
+        if (spec != null) {
+            cmds = new 
ArrayList<>(spec.commandLine().getParseResult().originalArgs());
+        } else {
+            cmds = new ArrayList<>();
+            cmds.add("run");
+            if (transformMessageRun) {
+                cmds.add("--empty");
+            }
+        }
 
         if (background) {
             cmds.remove("--background=true");
@@ -893,11 +924,16 @@ public class Run extends CamelCommand {
 
         ProcessBuilder pb = new ProcessBuilder();
         pb.command(jbangArgs);
+
+        System.out.println(jbangArgs);
+
         if (background) {
             Process p = pb.start();
             this.spawnPid = p.pid();
-            System.out.println("Running Camel integration: " + name + " 
(version: " + camelVersion
-                               + ") in background with PID: " + p.pid());
+            if (!silentRun) {
+                System.out.println("Running Camel integration: " + name + " 
(version: " + camelVersion
+                                   + ") in background with PID: " + p.pid());
+            }
             return 0;
         } else {
             pb.inheritIO(); // run in foreground (with IO so logs are visible)
@@ -909,7 +945,16 @@ public class Run extends CamelCommand {
     }
 
     protected int runBackground(KameletMain main) throws Exception {
-        List<String> cmds = new 
ArrayList<>(spec.commandLine().getParseResult().originalArgs());
+        List<String> cmds;
+        if (spec != null) {
+            cmds = new 
ArrayList<>(spec.commandLine().getParseResult().originalArgs());
+        } else {
+            cmds = new ArrayList<>();
+            cmds.add("run");
+            if (transformMessageRun) {
+                cmds.add("--empty");
+            }
+        }
 
         cmds.remove("--background=true");
         cmds.remove("--background");
@@ -920,7 +965,9 @@ public class Run extends CamelCommand {
         pb.command(cmds);
         Process p = pb.start();
         this.spawnPid = p.pid();
-        System.out.println("Running Camel integration: " + name + " in 
background with PID: " + p.pid());
+        if (!silentRun) {
+            System.out.println("Running Camel integration: " + name + " in 
background with PID: " + p.pid());
+        }
         return 0;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformCommand.java
new file mode 100644
index 00000000000..fb4f910af19
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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 = "transform",
+                     description = "Transform message or Camel routes (use 
transform --help to see sub commands)")
+public class TransformCommand extends CamelCommand {
+
+    public TransformCommand(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/Transform.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformRoute.java
similarity index 95%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Transform.java
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformRoute.java
index 6c5e47acab9..1b648000fd6 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Transform.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformRoute.java
@@ -29,8 +29,8 @@ import org.apache.camel.util.StopWatch;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 
-@Command(name = "transform", description = "Transform Camel routes to XML or 
YAML format", sortOptions = false)
-public class Transform extends CamelCommand {
+@Command(name = "route", description = "Transform Camel routes to XML or YAML 
format", sortOptions = false)
+public class TransformRoute extends CamelCommand {
 
     @CommandLine.Parameters(description = "The Camel file(s) to run. If no 
files specified then application.properties is used as source for which files 
to run.",
                             arity = "0..9", paramLabel = "<files>", 
parameterConsumer = FilesConsumer.class)
@@ -60,7 +60,7 @@ public class Transform extends CamelCommand {
                         description = "Whether to ignore route loading and 
compilation errors (use this with care!)")
     boolean ignoreLoadingError;
 
-    public Transform(CamelJBangMain main) {
+    public TransformRoute(CamelJBangMain main) {
         super(main);
     }
 
@@ -130,9 +130,9 @@ public class Transform extends CamelCommand {
         return null;
     }
 
-    static class FilesConsumer extends ParameterConsumer<Transform> {
+    static class FilesConsumer extends ParameterConsumer<TransformRoute> {
         @Override
-        protected void doConsumeParameters(Stack<String> args, Transform cmd) {
+        protected void doConsumeParameters(Stack<String> args, TransformRoute 
cmd) {
             String arg = args.pop();
             cmd.files.add(arg);
         }
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
new file mode 100644
index 00000000000..b958329d31e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/TransformMessageAction.java
@@ -0,0 +1,289 @@
+/*
+ * 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.io.File;
+import java.io.FileInputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+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.VersionHelper;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.TimeUtils;
+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 org.fusesource.jansi.AnsiConsole;
+import picocli.CommandLine;
+
[email protected](name = "message",
+                     description = "Transform message from one format to 
another via an existing running Camel integration",
+                     sortOptions = false)
+public class TransformMessageAction extends ActionWatchCommand {
+
+    @CommandLine.Option(names = { "--camel-version" },
+                        description = "To run using a different Camel version 
than the default version.")
+    String camelVersion;
+
+    @CommandLine.Option(names = { "--body" }, required = true,
+                        description = "Message body to send (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 = {
+            "--language" },
+                        required = true,
+                        description = "The language to use for message 
transformation")
+    private String language;
+
+    @CommandLine.Option(names = {
+            "--template" },
+                        required = true,
+                        description = "The template to use for message 
transformation (prefix with file: to refer to loading message body from file)")
+    private String template;
+
+    @CommandLine.Option(names = {
+            "--output" },
+                        description = "File to store output. If none provide 
then output is printed to console.")
+    private String output;
+
+    @CommandLine.Option(names = { "--show-exchange-properties" }, defaultValue 
= "false",
+                        description = "Show exchange properties from the 
output message")
+    boolean showExchangeProperties;
+
+    @CommandLine.Option(names = { "--show-headers" }, defaultValue = "true",
+                        description = "Show message headers from the output 
message")
+    boolean showHeaders = true;
+
+    @CommandLine.Option(names = { "--show-body" }, defaultValue = "true",
+                        description = "Show message body from the output 
message")
+    boolean showBody = true;
+
+    @CommandLine.Option(names = { "--show-exception" }, defaultValue = "true",
+                        description = "Show exception and stacktrace for 
failed transformation")
+    boolean showException = true;
+
+    @CommandLine.Option(names = { "--timeout" }, defaultValue = "20000",
+                        description = "Timeout in millis waiting for message 
to be transformed")
+    long timeout = 20000;
+
+    @CommandLine.Option(names = { "--logging-color" }, defaultValue = "true", 
description = "Use colored logging")
+    boolean loggingColor = true;
+
+    @CommandLine.Option(names = { "--pretty" },
+                        description = "Pretty print message body when using 
JSon or XML format")
+    boolean pretty;
+
+    private volatile long pid;
+
+    private MessageTableHelper tableHelper;
+
+    public TransformMessageAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        Integer exit;
+        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")) {
+                System.err.println("This requires Camel version 4.3 or newer");
+                return -1;
+            }
+            exit = run.runTransformMessage(camelVersion);
+            this.pid = run.spawnPid;
+            if (exit == 0) {
+                exit = super.doCall();
+            }
+        } finally {
+            if (pid > 0) {
+                // cleanup output file
+                File outputFile = getOutputFile(Long.toString(pid));
+                FileUtil.deleteFile(outputFile);
+                // stop running camel as we are done
+                File dir = new File(System.getProperty("user.home"), ".camel");
+                File pidFile = new File(dir, Long.toString(pid));
+                if (pidFile.exists()) {
+                    FileUtil.deleteFile(pidFile);
+                }
+            }
+        }
+
+        return exit;
+    }
+
+    @Override
+    protected Integer doWatchCall() throws Exception {
+        // ensure output file is deleted before executing action
+        File outputFile = getOutputFile(Long.toString(pid));
+        FileUtil.deleteFile(outputFile);
+
+        JsonObject root = new JsonObject();
+        root.put("action", "transform");
+        root.put("language", language);
+        root.put("template", Jsoner.escape(template));
+        root.put("body", Jsoner.escape(body));
+        if (headers != null) {
+            JsonArray arr = new JsonArray();
+            for (String h : headers) {
+                JsonObject jo = new JsonObject();
+                if (!h.contains("=")) {
+                    System.out.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);
+        }
+        File f = getActionFile(Long.toString(pid));
+        try {
+            IOHelper.writeText(root.toJson(), f);
+        } catch (Exception e) {
+            // ignore
+        }
+
+        JsonObject jo = waitForOutputFile(outputFile);
+        if (jo != null) {
+            printStatusLine(jo);
+            String exchangeId = jo.getString("exchangeId");
+            JsonObject message = jo.getMap("message");
+            JsonObject cause = jo.getMap("exception");
+            if (message != null || cause != null) {
+                if (output != null) {
+                    File target = new File(output);
+                    String json = jo.toJson();
+                    if (pretty) {
+                        json = Jsoner.prettyPrint(json, 2);
+                    }
+                    IOHelper.writeText(json, target);
+                }
+                if (!showExchangeProperties && message != null) {
+                    message.remove("exchangeProperties");
+                }
+                if (!showHeaders && message != null) {
+                    message.remove("headers");
+                }
+                if (!showBody && message != null) {
+                    message.remove("body");
+                }
+                if (!showException && cause != null) {
+                    cause = null;
+                }
+                if (output == null) {
+                    if (watch) {
+                        clearScreen();
+                    }
+                    tableHelper = new MessageTableHelper();
+                    tableHelper.setPretty(pretty);
+                    tableHelper.setLoggingColor(loggingColor);
+                    
tableHelper.setShowExchangeProperties(showExchangeProperties);
+                    String table = tableHelper.getDataAsTable(exchangeId, 
"InOut", null, message, cause);
+                    System.out.println(table);
+                }
+            }
+        }
+
+        // delete output file after use
+        FileUtil.deleteFile(outputFile);
+
+        return 0;
+    }
+
+    private void printStatusLine(JsonObject jo) {
+        // timstamp
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        String ts = sdf.format(new Date(jo.getLong("timestamp")));
+        if (loggingColor) {
+            
AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(ts).reset());
+        } else {
+            System.out.print(ts);
+        }
+        // pid
+        System.out.print("  ");
+        String p = String.format("%5.5s", this.pid);
+        if (loggingColor) {
+            AnsiConsole.out().print(Ansi.ansi().fgMagenta().a(p).reset());
+            
AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a("
 --- ").reset());
+        } else {
+            System.out.print(p);
+            System.out.print(" --- ");
+        }
+        // status
+        System.out.print(getStatus(jo));
+        // elapsed
+        String e = TimeUtils.printDuration(jo.getLong("elapsed"), true);
+        if (loggingColor) {
+            AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(" (" + e + 
")").reset());
+        } else {
+            System.out.print("(" + e + ")");
+        }
+        System.out.println();
+    }
+
+    private String getStatus(JsonObject r) {
+        boolean failed = "failed".equals(r.getString("status"));
+        String status;
+        if (failed) {
+            status = "Failed (exception)";
+        } else if (output != null) {
+            status = "Output saved to file (success)";
+        } else {
+            status = "Message transformed (success)";
+        }
+        if (loggingColor) {
+            return Ansi.ansi().fg(failed ? Ansi.Color.RED : 
Ansi.Color.GREEN).a(status).reset().toString();
+        } else {
+            return status;
+        }
+    }
+
+    protected JsonObject waitForOutputFile(File outputFile) {
+        StopWatch watch = new StopWatch();
+        while (watch.taken() < timeout) {
+            try {
+                // give time for response to be ready
+                Thread.sleep(20);
+
+                if (outputFile.exists()) {
+                    FileInputStream fis = new FileInputStream(outputFile);
+                    String text = IOHelper.loadText(fis);
+                    IOHelper.close(fis);
+                    return (JsonObject) Jsoner.deserialize(text);
+                }
+
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return null;
+    }
+}


Reply via email to