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 9f10cef4906 Transform message command (#12209)
9f10cef4906 is described below
commit 9f10cef4906d68f625bf712087c2c755f18a9369
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 27 10:15:25 2023 +0100
Transform message command (#12209)
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 | 77 ++++--
.../dsl/jbang/core/commands/TransformCommand.java | 35 +++
.../{Transform.java => TransformRoute.java} | 10 +-
.../commands/action/TransformMessageAction.java | 289 +++++++++++++++++++++
8 files changed, 617 insertions(+), 38 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..7876bd6ee2e 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,14 @@ public class Run extends CamelCommand {
ProcessBuilder pb = new ProcessBuilder();
pb.command(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 +943,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 +963,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;
}
@@ -995,8 +1040,10 @@ public class Run extends CamelCommand {
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)
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;
+ }
+}