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
commit 3a26c35414c3b5536d6efa998128949a32fdaffe Author: Claus Ibsen <[email protected]> AuthorDate: Fri Apr 14 08:42:58 2023 +0200 CAMEL-19236: camel-jbang - Command to send a message. --- .../org/apache/camel/support/MessageHelper.java | 79 ++-- .../camel/cli/connector/LocalCliConnector.java | 143 ++++++++ .../dsl/jbang/core/commands/CamelJBangMain.java | 2 + .../core/commands/action/CamelSendAction.java | 272 ++++++++++++++ .../core/commands/action/CamelTraceAction.java | 341 ++--------------- .../core/commands/action/MessageTableHelper.java | 408 +++++++++++++++++++++ 6 files changed, 902 insertions(+), 343 deletions(-) diff --git a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java index cbc6289d849..e903dbdb25a 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java @@ -808,7 +808,7 @@ public final class MessageHelper { } /** - * Dumps the message as a generic JSon structure. + * Dumps the message as a generic JSon structure as text. * * @param message the message * @return the JSon @@ -818,7 +818,7 @@ public final class MessageHelper { } /** - * Dumps the message as a generic JSon structure. + * Dumps the message as a generic JSon structure as text. * * @param message the message * @param includeBody whether or not to include the message body @@ -829,7 +829,7 @@ public final class MessageHelper { } /** - * Dumps the message as a generic JSon structure. + * Dumps the message as a generic JSon structure as text. * * @param message the message * @param includeBody whether or not to include the message body @@ -841,7 +841,7 @@ public final class MessageHelper { } /** - * Dumps the message as a generic JSon structure. + * Dumps the message as a generic JSon structure as text. * * @param message the message * @param includeBody whether or not to include the message body @@ -859,7 +859,7 @@ public final class MessageHelper { } /** - * Dumps the message as a generic JSon structure. + * Dumps the message as a generic JSon structure as text. * * @param message the message * @param includeExchangeProperties whether or not to include exchange properties @@ -877,6 +877,35 @@ public final class MessageHelper { Message message, boolean includeExchangeProperties, boolean includeBody, int indent, boolean allowCachedStreams, boolean allowStreams, boolean allowFiles, int maxChars, boolean pretty) { + JsonObject jo = dumpAsJSonObject(message, includeExchangeProperties, includeBody, allowCachedStreams, allowStreams, allowFiles, maxChars); + String answer = jo.toJson(); + if (pretty) { + if (indent > 0) { + answer = Jsoner.prettyPrint(answer, indent); + } else { + answer = Jsoner.prettyPrint(answer); + } + } + return answer; + } + + /** + * Dumps the message as a generic JSon Object. + * + * @param message the message + * @param includeExchangeProperties whether or not to include exchange properties + * @param includeBody whether or not to include the message body + * @param allowCachedStreams whether to include message body if they are stream cached based + * @param allowStreams whether to include message body if they are stream based + * @param allowFiles whether to include message body if they are file based + * @param maxChars clip body after maximum chars (to avoid very big messages). Use 0 or negative + * value to not limit at all. + * @return the JSon Object + */ + public static JsonObject dumpAsJSonObject( + Message message, boolean includeExchangeProperties, boolean includeBody, + boolean allowCachedStreams, boolean allowStreams, boolean allowFiles, int maxChars) { + JsonObject root = new JsonObject(); JsonObject jo = new JsonObject(); root.put("message", jo); @@ -981,15 +1010,7 @@ public final class MessageHelper { } } - String answer = root.toJson(); - if (pretty) { - if (indent > 0) { - answer = Jsoner.prettyPrint(answer, indent); - } else { - answer = Jsoner.prettyPrint(answer); - } - } - return answer; + return root; } /** @@ -1029,13 +1050,31 @@ public final class MessageHelper { } /** - * Dumps the exception as a generic JSon structure. + * Dumps the exception as a generic JSon structure as text. * * @param indent number of spaces to indent * @param pretty whether to pretty print JSon * @return the JSon */ public static String dumpExceptionAsJSon(Throwable exception, int indent, boolean pretty) { + JsonObject jo = dumpExceptionAsJSonObject(exception); + String answer = jo.toJson(); + if (pretty) { + if (indent > 0) { + answer = Jsoner.prettyPrint(answer, indent); + } else { + answer = Jsoner.prettyPrint(answer); + } + } + return answer; + } + + /** + * Dumps the exception as a generic JSon object. + * + * @return the JSon object + */ + public static JsonObject dumpExceptionAsJSonObject(Throwable exception) { JsonObject root = new JsonObject(); JsonObject jo = new JsonObject(); root.put("exception", jo); @@ -1058,15 +1097,7 @@ public final class MessageHelper { } catch (Throwable e) { // ignore as the body is for logging purpose } - String answer = root.toJson(); - if (pretty) { - if (indent > 0) { - answer = Jsoner.prettyPrint(answer, indent); - } else { - answer = Jsoner.prettyPrint(answer); - } - } - return answer; + return root; } } 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 b63e17482d9..6dd479e5bd0 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 @@ -18,12 +18,15 @@ package org.apache.camel.cli.connector; import java.io.File; import java.io.FileInputStream; +import java.io.InputStream; import java.lang.management.ClassLoadingMXBean; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -37,7 +40,12 @@ import java.util.stream.Collectors; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; +import org.apache.camel.Endpoint; import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.NoSuchEndpointException; +import org.apache.camel.Processor; +import org.apache.camel.ProducerTemplate; import org.apache.camel.Route; import org.apache.camel.api.management.ManagedCamelContext; import org.apache.camel.console.DevConsole; @@ -46,12 +54,15 @@ import org.apache.camel.spi.CliConnector; import org.apache.camel.spi.CliConnectorFactory; import org.apache.camel.spi.ContextReloadStrategy; import org.apache.camel.support.DefaultContextReloadStrategy; +import org.apache.camel.support.EndpointHelper; +import org.apache.camel.support.MessageHelper; import org.apache.camel.support.PatternHelper; import org.apache.camel.support.PluginHelper; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; +import org.apache.camel.util.StopWatch; import org.apache.camel.util.concurrent.ThreadHelper; import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; @@ -66,6 +77,8 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C private static final Logger LOG = LoggerFactory.getLogger(LocalCliConnector.class); + private static final int BODY_MAX_CHARS = 128 * 1024; + private final CliConnectorFactory cliConnectorFactory; private CamelContext camelContext; private int delay = 2000; @@ -75,6 +88,7 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C private final AtomicBoolean terminating = new AtomicBoolean(); private ScheduledExecutorService executor; private volatile ExecutorService terminateExecutor; + private ProducerTemplate producer; private File lockFile; private File statusFile; private File actionFile; @@ -126,6 +140,7 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C } } platformVersion = cliConnectorFactory.getRuntimeVersion(); + producer = camelContext.createProducerTemplate(); // create thread from JDK so it is not managed by Camel because we want the pool to be independent when // camel is being stopped which otherwise can lead to stopping the thread pool while the task is running @@ -294,6 +309,133 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C LOG.trace("Updating output file: {}", outputFile); IOHelper.writeText(json.toJson(), outputFile); } + } else if ("send".equals(action)) { + StopWatch watch = new StopWatch(); + long timestamp = System.currentTimeMillis(); + String endpoint = root.getString("endpoint"); + String body = root.getString("body"); + String exchangePattern = root.getString("exchangePattern"); + Collection<JsonObject> headers = root.getCollection("headers"); + if (body != null) { + 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 = is; + } + if (headers != null) { + map = new HashMap<>(); + for (JsonObject jo : headers) { + map.put(jo.getString("key"), jo.getString("value")); + } + } + final Object inputBody = b; + final Map<String, Object> inputHeaders = map; + Exchange out; + Endpoint target = null; + if (endpoint == null) { + List<Route> routes = camelContext.getRoutes(); + if (!routes.isEmpty()) { + // grab endpoint from 1st route + target = routes.get(0).getEndpoint(); + } + } else { + // is the endpoint a pattern or route id + boolean scheme = endpoint.contains(":"); + boolean pattern = endpoint.endsWith("*"); + if (!scheme || pattern) { + if (!scheme) { + endpoint = endpoint + "*"; + } + for (Route route : camelContext.getRoutes()) { + Endpoint e = route.getEndpoint(); + if (EndpointHelper.matchEndpoint(camelContext, e.getEndpointUri(), endpoint)) { + target = e; + break; + } + } + if (target == null) { + // okay it may refer to a route id + for (Route route : camelContext.getRoutes()) { + String id = route.getRouteId(); + Endpoint e = route.getEndpoint(); + if (EndpointHelper.matchEndpoint(camelContext, id, endpoint)) { + target = e; + break; + } + } + } + } else { + target = camelContext.getEndpoint(endpoint); + } + } + + if (target != null) { + out = producer.send(target, new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getMessage().setBody(inputBody); + if (inputHeaders != null) { + exchange.getMessage().setHeaders(inputHeaders); + } + exchange.setPattern( + "InOut".equals(exchangePattern) ? ExchangePattern.InOut : ExchangePattern.InOnly); + } + }); + IOHelper.close(is); + LOG.trace("Updating output file: {}", outputFile); + if (out.getException() != null) { + JsonObject jo = new JsonObject(); + jo.put("endpoint", target.getEndpointUri()); + jo.put("exchangeId", out.getExchangeId()); + jo.put("exchangePattern", exchangePattern); + 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 if ("InOut".equals(exchangePattern)) { + JsonObject jo = new JsonObject(); + jo.put("endpoint", target.getEndpointUri()); + jo.put("exchangeId", out.getExchangeId()); + jo.put("exchangePattern", exchangePattern); + 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); + } else { + JsonObject jo = new JsonObject(); + jo.put("endpoint", target.getEndpointUri()); + jo.put("exchangeId", out.getExchangeId()); + jo.put("exchangePattern", exchangePattern); + jo.put("timestamp", timestamp); + jo.put("elapsed", watch.taken()); + jo.put("status", "success"); + IOHelper.writeText(jo.toJson(), outputFile); + } + } else { + // there is no valid endpoint + JsonObject jo = new JsonObject(); + jo.put("endpoint", root.getString("endpoint")); + jo.put("exchangeId", ""); + jo.put("exchangePattern", exchangePattern); + jo.put("timestamp", timestamp); + jo.put("elapsed", watch.taken()); + jo.put("status", "failed"); + // avoid double wrap + jo.put("exception", + MessageHelper.dumpExceptionAsJSonObject(new NoSuchEndpointException(root.getString("endpoint"))) + .getMap("exception")); + IOHelper.writeText(jo.toJson(), outputFile); + } + } } // action done so delete file @@ -629,6 +771,7 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C camelContext.getExecutorServiceManager().shutdown(executor); executor = null; } + ServiceHelper.stopService(producer); } private static String getPid() { 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 d0b9bfa7e9f..f536129e90d 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 @@ -27,6 +27,7 @@ import org.apache.camel.dsl.jbang.core.commands.action.CamelReloadAction; import org.apache.camel.dsl.jbang.core.commands.action.CamelResetStatsAction; import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteStartAction; import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteStopAction; +import org.apache.camel.dsl.jbang.core.commands.action.CamelSendAction; import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceAction; import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceTop; import org.apache.camel.dsl.jbang.core.commands.action.CamelThreadDump; @@ -114,6 +115,7 @@ public class CamelJBangMain implements Callable<Integer> { .addSubcommand("stop-route", new CommandLine(new CamelRouteStopAction(main))) .addSubcommand("reset-stats", new CommandLine(new CamelResetStatsAction(main))) .addSubcommand("reload", new CommandLine(new CamelReloadAction(main))) + .addSubcommand("send", new CommandLine(new CamelSendAction(main))) .addSubcommand("thread-dump", new CommandLine(new CamelThreadDump(main))) .addSubcommand("logger", new CommandLine(new LoggerAction(main))) .addSubcommand("gc", new CommandLine(new CamelGCAction(main)))) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java new file mode 100644 index 00000000000..a8cdf8d650e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java @@ -0,0 +1,272 @@ +/* + * 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.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 = "send", + description = "Sends a message to a system via an existing running Camel integration") +public class CamelSendAction extends ActionBaseCommand { + + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "1") + String name; + + @CommandLine.Option(names = { "--endpoint" }, + description = "Endpoint where to send the message (can be uri, pattern, or refer to a route id)") + String endpoint; + + @CommandLine.Option(names = { "--reply" }, + description = "Whether to expect a reply message (InOut vs InOut messaging style)") + boolean reply; + + @CommandLine.Option(names = { "--reply-file" }, + description = "Saves reply message to the file with the given name (override if exists)") + String replyFile; + + @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 = { "--timeout" }, defaultValue = "20000", + description = "Timeout in millis waiting for message to be sent (and reply message if InOut messaging)") + long timeout = 20000; + + @CommandLine.Option(names = { "--show-exchange-properties" }, defaultValue = "false", + description = "Show exchange properties in traced messages") + boolean showExchangeProperties; + + @CommandLine.Option(names = { "--show-headers" }, defaultValue = "true", + description = "Show message headers in traced messages") + boolean showHeaders = true; + + @CommandLine.Option(names = { "--show-body" }, defaultValue = "true", + description = "Show message body in traced messages") + boolean showBody = true; + + @CommandLine.Option(names = { "--show-exception" }, defaultValue = "true", + description = "Show exception and stacktrace for failed messages") + boolean showException = true; + + @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 CamelSendAction(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + List<Long> pids = findPids(name); + if (pids.isEmpty()) { + return 0; + } else if (pids.size() > 1) { + System.out.println("Name or pid " + name + " matches " + pids.size() + + " running Camel integrations. Specify a name or PID that matches exactly one."); + return 0; + } + + this.pid = pids.get(0); + + // ensure output file is deleted before executing action + File outputFile = getOutputFile(Long.toString(pid)); + FileUtil.deleteFile(outputFile); + + JsonObject root = new JsonObject(); + root.put("action", "send"); + root.put("endpoint", endpoint); + String mep = (reply || replyFile != null) ? "InOut" : "InOnly"; + root.put("exchangePattern", mep); + root.put("body", 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 (replyFile != null) { + File target = new File(replyFile); + 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 (replyFile == null) { + tableHelper = new MessageTableHelper(); + tableHelper.setPretty(pretty); + tableHelper.setLoggingColor(loggingColor); + tableHelper.setShowExchangeProperties(showExchangeProperties); + String table = tableHelper.getDataAsTable(exchangeId, mep, jo, 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(" --- "); + } + // endpoint + String ids = jo.getString("endpoint"); + if (ids.length() > 40) { + ids = ids.substring(ids.length() - 40); + } + ids = String.format("%40.40s", ids); + if (loggingColor) { + AnsiConsole.out().print(Ansi.ansi().fgCyan().a(ids).reset()); + } else { + System.out.print(ids); + } + 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")); + boolean reply = r.containsKey("message"); + String status; + if (failed) { + status = "Failed (exception)"; + } else if (replyFile != null) { + status = "Reply saved to file (success)"; + } else if (reply) { + status = "Reply received (success)"; + } else { + status = "Sent (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; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java index 97de403abc0..6b449764e43 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelTraceAction.java @@ -23,7 +23,6 @@ import java.io.LineNumberReader; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -36,15 +35,9 @@ import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.regex.Pattern; -import com.github.freva.asciitable.AsciiTable; -import com.github.freva.asciitable.Column; -import com.github.freva.asciitable.HorizontalAlign; -import com.github.freva.asciitable.OverflowBehaviour; import org.apache.camel.catalog.impl.TimePatternConverter; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; -import org.apache.camel.dsl.jbang.core.common.JSonHelper; import org.apache.camel.dsl.jbang.core.common.ProcessHelper; -import org.apache.camel.dsl.jbang.core.common.XmlHelper; import org.apache.camel.util.StopWatch; import org.apache.camel.util.StringHelper; import org.apache.camel.util.TimeUtils; @@ -152,6 +145,8 @@ public class CamelTraceAction extends ActionBaseCommand { private int nameMaxWidth; private boolean prefixShown; + private MessageTableHelper tableHelper; + private final Map<String, Ansi.Color> nameColors = new HashMap<>(); private final Map<String, Ansi.Color> exchangeIdColors = new HashMap<>(); private int exchangeIdColorsIndex = 1; @@ -162,6 +157,25 @@ public class CamelTraceAction extends ActionBaseCommand { @Override public Integer doCall() throws Exception { + // setup table helper + tableHelper = new MessageTableHelper(); + tableHelper.setPretty(pretty); + tableHelper.setLoggingColor(loggingColor); + tableHelper.setShowExchangeProperties(showExchangeProperties); + tableHelper.setExchangeIdColorChooser(value -> { + Ansi.Color color = exchangeIdColors.get(value); + if (color == null) { + // grab a new color + exchangeIdColorsIndex++; + if (exchangeIdColorsIndex > 6) { + exchangeIdColorsIndex = 2; + } + color = Ansi.Color.values()[exchangeIdColorsIndex]; + exchangeIdColors.put(value, color); + } + return color; + }); + Map<Long, Pid> pids = new LinkedHashMap<>(); if (latest) { @@ -675,130 +689,7 @@ public class CamelTraceAction extends ActionBaseCommand { } private String getDataAsTable(Row r) { - List<TableRow> rows = new ArrayList<>(); - - TableRow eRow = new TableRow("Exchange", r.message.getString("exchangeType"), r.exchangePattern, r.exchangeId); - String tab1 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), - new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString))); - String tab1b = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.CENTER) - .minWidth(18).maxWidth(18).with(TableRow::mepAsKey), - new Column().dataAlign(HorizontalAlign.RIGHT) - .maxWidth(80).with(TableRow::exchangeIdAsValue))); - // exchange properties - JsonArray arr = r.message.getCollection("exchangeProperties"); - if (arr != null) { - for (Object o : arr) { - JsonObject jo = (JsonObject) o; - rows.add(new TableRow("Property", jo.getString("type"), jo.getString("key"), jo.get("value"))); - } - } - // internal exchange properties - arr = r.message.getCollection("internalExchangeProperties"); - if (arr != null) { - for (Object o : arr) { - JsonObject jo = (JsonObject) o; - rows.add(new TableRow("Property", jo.getString("type"), jo.getString("key"), jo.get("value"))); - } - } - String tab2 = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(25).maxWidth(50, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), - new Column().dataAlign(HorizontalAlign.RIGHT) - .minWidth(25).maxWidth(40, OverflowBehaviour.NEWLINE).with(TableRow::keyAsString), - new Column().dataAlign(HorizontalAlign.LEFT) - .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsString))); - rows.clear(); - - // message type before headers - TableRow msgRow = new TableRow("Message", r.message.getString("messageType"), null, null); - String tab3 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(msgRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), - new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString))); - arr = r.message.getCollection("headers"); - if (arr != null) { - for (Object o : arr) { - JsonObject jo = (JsonObject) o; - rows.add(new TableRow("Header", jo.getString("type"), jo.getString("key"), jo.get("value"))); - } - } - // headers - String tab4 = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(25).maxWidth(50, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), - new Column().dataAlign(HorizontalAlign.RIGHT) - .minWidth(25).maxWidth(40, OverflowBehaviour.NEWLINE).with(TableRow::keyAsString), - new Column().dataAlign(HorizontalAlign.LEFT) - .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsString))); - - // body and type - JsonObject jo = r.message.getMap("body"); - TableRow bodyRow = new TableRow("Body", jo.getString("type"), null, jo.get("value"), jo.getLong("position")); - String tab5 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(bodyRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), - new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAndLengthAsString))); - // body value only (span) - String tab6 = null; - if (bodyRow.value != null) { - tab6 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(bodyRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT).maxWidth(160, OverflowBehaviour.NEWLINE) - .with(b -> pretty ? bodyRow.valueAsStringPretty() : bodyRow.valueAsString()))); - } - String tab7 = null; - jo = r.exception; - if (jo != null) { - eRow = new TableRow("Exception", jo.getString("type"), null, jo.get("message")); - tab7 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT) - .minWidth(showExchangeProperties ? 12 : 10) - .with(TableRow::kindAsStringRed), - new Column().dataAlign(HorizontalAlign.LEFT) - .maxWidth(40, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), - new Column().dataAlign(HorizontalAlign.LEFT) - .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsStringRed))); - } - // stacktrace only (span) - String tab8 = null; - if (jo != null) { - eRow = new TableRow("Stacktrace", null, null, jo.get("stackTrace")); - tab8 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( - new Column().dataAlign(HorizontalAlign.LEFT).maxWidth(160, OverflowBehaviour.NEWLINE) - .with(TableRow::valueAsStringRed))); - } - String answer = ""; - if (tab1 != null && tab1b != null && !tab1.isEmpty()) { - answer = answer + tab1 + tab1b + System.lineSeparator(); - } - if (tab2 != null && !tab2.isEmpty()) { - answer = answer + tab2 + System.lineSeparator(); - } - if (tab3 != null && !tab3.isEmpty()) { - answer = answer + tab3 + System.lineSeparator(); - } - if (tab4 != null && !tab4.isEmpty()) { - answer = answer + tab4 + System.lineSeparator(); - } - if (tab5 != null && !tab5.isEmpty()) { - answer = answer + tab5 + System.lineSeparator(); - } - if (tab6 != null && !tab6.isEmpty()) { - answer = answer + tab6 + System.lineSeparator(); - } - if (tab7 != null && !tab7.isEmpty()) { - answer = answer + tab7 + System.lineSeparator(); - } - if (tab8 != null && !tab8.isEmpty()) { - answer = answer + tab8 + System.lineSeparator(); - } - return answer; + return tableHelper.getDataAsTable(r.exchangeId, r.exchangePattern, null, r.message, r.exception); } private String getElapsed(Row r) { @@ -891,192 +782,4 @@ public class CamelTraceAction extends ActionBaseCommand { } - private class TableRow { - String kind; - String type; - String key; - Object value; - Long position; - - TableRow(String kind, String type, String key, Object value) { - this(kind, type, key, value, null); - } - - TableRow(String kind, String type, String key, Object value, Long position) { - this.kind = kind; - this.type = type; - this.key = key; - this.value = value; - this.position = position; - } - - String valueAsString() { - return value != null ? value.toString() : "null"; - } - - String valueAsStringPretty() { - if (value == null) { - return "null"; - } - boolean json = false; - String s = value.toString(); - if (!s.isEmpty()) { - try { - s = Jsoner.unescape(s); - if (loggingColor) { - s = JSonHelper.colorPrint(s, 2, true); - } else { - s = JSonHelper.prettyPrint(s, 2); - } - if (s != null && !s.isEmpty()) { - json = true; - } - } catch (Throwable e) { - // ignore as not json - } - if (s == null || s.isEmpty()) { - s = value.toString(); - } - if (!json) { - // try with xml - try { - s = Jsoner.unescape(s); - if (loggingColor) { - s = XmlHelper.colorPrint(s, 2, true); - } else { - s = XmlHelper.prettyPrint(s, 2); - } - } catch (Throwable e) { - // ignore as not xml - } - } - if (s == null || s.isEmpty()) { - s = value.toString(); - } - } - if (s == null) { - return "null"; - } - return s; - } - - String valueAsStringRed() { - if (value != null) { - if (loggingColor) { - return Ansi.ansi().fgRed().a(value).reset().toString(); - } else { - return value.toString(); - } - } - return ""; - } - - String keyAsString() { - if (key == null) { - return ""; - } - return key; - } - - String kindAsString() { - return kind; - } - - String kindAsStringRed() { - if (loggingColor) { - return Ansi.ansi().fgRed().a(kind).reset().toString(); - } else { - return kind; - } - } - - String typeAsString() { - String s; - if (type == null) { - s = "null"; - } else if (type.startsWith("java.util.concurrent")) { - s = type.substring(21); - } else if (type.startsWith("java.lang.") || type.startsWith("java.util.")) { - s = type.substring(10); - } else if (type.startsWith("org.apache.camel.support.")) { - s = type.substring(25); - } else if (type.startsWith("org.apache.camel.converter.stream.")) { - s = type.substring(34); - } else { - s = type; - } - s = "(" + s + ")"; - if (loggingColor) { - s = Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); - } - return s; - } - - String typeAndLengthAsString() { - String s; - if (type == null) { - s = "null"; - } else if (type.startsWith("java.util.concurrent")) { - s = type.substring(21); - } else if (type.startsWith("java.lang.") || type.startsWith("java.util.")) { - s = type.substring(10); - } else if (type.startsWith("org.apache.camel.support.")) { - s = type.substring(25); - } else if (type.startsWith("org.apache.camel.converter.stream.")) { - s = type.substring(34); - } else { - s = type; - } - s = "(" + s + ")"; - int l = valueLength(); - long p = position != null ? position : -1; - if (l != -1 & p != -1) { - s = s + " (pos: " + p + " length: " + l + ")"; - } else if (l != -1) { - s = s + " (length: " + l + ")"; - } else if (p != -1) { - s = s + " (pos: " + p + ")"; - } - if (loggingColor) { - s = Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); - } - return s; - } - - String mepAsKey() { - String s = key; - if (loggingColor) { - s = Ansi.ansi().fgBrightMagenta().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); - } - return s; - } - - String exchangeIdAsValue() { - String s = value.toString(); - if (loggingColor) { - Ansi.Color color = exchangeIdColors.get(s); - if (color == null) { - // grab a new color - exchangeIdColorsIndex++; - if (exchangeIdColorsIndex > 6) { - exchangeIdColorsIndex = 2; - } - color = Ansi.Color.values()[exchangeIdColorsIndex]; - exchangeIdColors.put(s, color); - } - s = Ansi.ansi().fg(color).a(s).reset().toString(); - } - return s; - } - - int valueLength() { - if (value == null) { - return -1; - } else { - return valueAsString().length(); - } - } - - } - } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java new file mode 100644 index 00000000000..cf7282e899a --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java @@ -0,0 +1,408 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.github.freva.asciitable.AsciiTable; +import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.HorizontalAlign; +import com.github.freva.asciitable.OverflowBehaviour; +import org.apache.camel.dsl.jbang.core.common.JSonHelper; +import org.apache.camel.dsl.jbang.core.common.XmlHelper; +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; + +/** + * Helper to output message details (headers, body) in a table like structure with pretty and color supported. + */ +public class MessageTableHelper { + + @FunctionalInterface + interface ColorChooser { + Ansi.Color color(String value); + } + + private boolean loggingColor; + private boolean pretty; + private boolean showExchangeProperties; + private ColorChooser exchangeIdColorChooser; + + public boolean isLoggingColor() { + return loggingColor; + } + + public void setLoggingColor(boolean loggingColor) { + this.loggingColor = loggingColor; + } + + public boolean isPretty() { + return pretty; + } + + public void setPretty(boolean pretty) { + this.pretty = pretty; + } + + public boolean isShowExchangeProperties() { + return showExchangeProperties; + } + + public void setShowExchangeProperties(boolean showExchangeProperties) { + this.showExchangeProperties = showExchangeProperties; + } + + public ColorChooser getExchangeIdColorChooser() { + return exchangeIdColorChooser; + } + + public void setExchangeIdColorChooser(ColorChooser exchangeIdColorChooser) { + this.exchangeIdColorChooser = exchangeIdColorChooser; + } + + public String getDataAsTable( + String exchangeId, String exchangePattern, + JsonObject endpoint, JsonObject root, JsonObject cause) { + + List<TableRow> rows = new ArrayList<>(); + TableRow eRow; + String tab0 = null, tab1 = null, tab1b = null, tab2 = null, tab3 = null, tab4 = null, tab5 = null, tab6 = null; + + if (endpoint != null) { + eRow = new TableRow("Endpoint", endpoint.getString("endpoint"), null, null); + tab0 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString))); + } + + if (root != null) { + eRow = new TableRow("Exchange", root.getString("exchangeType"), exchangePattern, exchangeId); + tab1 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString))); + tab1b = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.CENTER) + .minWidth(18).maxWidth(18).with(TableRow::mepAsKey), + new Column().dataAlign(HorizontalAlign.RIGHT) + .maxWidth(80).with(TableRow::exchangeIdAsValue))); + // exchange properties + JsonArray arr = root.getCollection("exchangeProperties"); + if (arr != null) { + for (Object o : arr) { + JsonObject jo = (JsonObject) o; + rows.add(new TableRow("Property", jo.getString("type"), jo.getString("key"), jo.get("value"))); + } + } + // internal exchange properties + arr = root.getCollection("internalExchangeProperties"); + if (arr != null) { + for (Object o : arr) { + JsonObject jo = (JsonObject) o; + rows.add(new TableRow("Property", jo.getString("type"), jo.getString("key"), jo.get("value"))); + } + } + tab2 = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(25).maxWidth(50, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), + new Column().dataAlign(HorizontalAlign.RIGHT) + .minWidth(25).maxWidth(40, OverflowBehaviour.NEWLINE).with(TableRow::keyAsString), + new Column().dataAlign(HorizontalAlign.LEFT) + .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsString))); + rows.clear(); + + // message type before headers + TableRow msgRow = new TableRow("Message", root.getString("messageType"), null, null); + tab3 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(msgRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString))); + arr = root.getCollection("headers"); + if (arr != null) { + for (Object o : arr) { + JsonObject jo = (JsonObject) o; + rows.add(new TableRow("Header", jo.getString("type"), jo.getString("key"), jo.get("value"))); + } + } + // headers + tab4 = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(25).maxWidth(50, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), + new Column().dataAlign(HorizontalAlign.RIGHT) + .minWidth(25).maxWidth(40, OverflowBehaviour.NEWLINE).with(TableRow::keyAsString), + new Column().dataAlign(HorizontalAlign.LEFT) + .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsString))); + + // body and type + JsonObject jo = root.getMap("body"); + if (jo != null) { + TableRow bodyRow = new TableRow("Body", jo.getString("type"), null, jo.get("value"), jo.getLong("position")); + tab5 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(bodyRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10).with(TableRow::kindAsString), + new Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAndLengthAsString))); + // body value only (span) + if (bodyRow.value != null) { + tab6 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(bodyRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT).maxWidth(160, OverflowBehaviour.NEWLINE) + .with(b -> pretty ? bodyRow.valueAsStringPretty() : bodyRow.valueAsString()))); + } + } + } + + String tab7 = null; + if (cause != null) { + eRow = new TableRow("Exception", cause.getString("type"), null, cause.get("message")); + tab7 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT) + .minWidth(showExchangeProperties ? 12 : 10) + .with(TableRow::kindAsStringRed), + new Column().dataAlign(HorizontalAlign.LEFT) + .maxWidth(40, OverflowBehaviour.CLIP_LEFT).with(TableRow::typeAsString), + new Column().dataAlign(HorizontalAlign.LEFT) + .maxWidth(80, OverflowBehaviour.NEWLINE).with(TableRow::valueAsStringRed))); + } + // stacktrace only (span) + String tab8 = null; + if (cause != null) { + String value = cause.getString("stackTrace"); + value = Jsoner.unescape(value); + eRow = new TableRow("Stacktrace", null, null, value); + tab8 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), Arrays.asList( + new Column().dataAlign(HorizontalAlign.LEFT).maxWidth(160, OverflowBehaviour.NEWLINE) + .with(TableRow::valueAsStringRed))); + } + String answer = ""; + if (tab0 != null && !tab0.isEmpty()) { + answer = answer + tab0 + System.lineSeparator(); + } + if (tab1 != null && tab1b != null && !tab1.isEmpty()) { + answer = answer + tab1 + tab1b + System.lineSeparator(); + } + if (tab2 != null && !tab2.isEmpty()) { + answer = answer + tab2 + System.lineSeparator(); + } + if (tab3 != null && !tab3.isEmpty()) { + answer = answer + tab3 + System.lineSeparator(); + } + if (tab4 != null && !tab4.isEmpty()) { + answer = answer + tab4 + System.lineSeparator(); + } + if (tab5 != null && !tab5.isEmpty()) { + answer = answer + tab5 + System.lineSeparator(); + } + if (tab6 != null && !tab6.isEmpty()) { + answer = answer + tab6 + System.lineSeparator(); + } + if (tab7 != null && !tab7.isEmpty()) { + answer = answer + tab7 + System.lineSeparator(); + } + if (tab8 != null && !tab8.isEmpty()) { + answer = answer + tab8 + System.lineSeparator(); + } + return answer; + } + + private class TableRow { + String kind; + String type; + String key; + Object value; + Long position; + + TableRow(String kind, String type, String key, Object value) { + this(kind, type, key, value, null); + } + + TableRow(String kind, String type, String key, Object value, Long position) { + this.kind = kind; + this.type = type; + this.key = key; + this.value = value; + this.position = position; + } + + String valueAsString() { + return value != null ? value.toString() : "null"; + } + + String valueAsStringPretty() { + if (value == null) { + return "null"; + } + boolean json = false; + String s = value.toString(); + if (!s.isEmpty()) { + try { + s = Jsoner.unescape(s); + if (loggingColor) { + s = JSonHelper.colorPrint(s, 2, true); + } else { + s = JSonHelper.prettyPrint(s, 2); + } + if (s != null && !s.isEmpty()) { + json = true; + } + } catch (Throwable e) { + // ignore as not json + } + if (s == null || s.isEmpty()) { + s = value.toString(); + } + if (!json) { + // try with xml + try { + s = Jsoner.unescape(s); + if (loggingColor) { + s = XmlHelper.colorPrint(s, 2, true); + } else { + s = XmlHelper.prettyPrint(s, 2); + } + } catch (Throwable e) { + // ignore as not xml + } + } + if (s == null || s.isEmpty()) { + s = value.toString(); + } + } + if (s == null) { + return "null"; + } + return s; + } + + String valueAsStringRed() { + if (value != null) { + if (loggingColor) { + return Ansi.ansi().fgRed().a(value).reset().toString(); + } else { + return value.toString(); + } + } + return ""; + } + + String keyAsString() { + if (key == null) { + return ""; + } + return key; + } + + String kindAsString() { + return kind; + } + + String kindAsStringRed() { + if (loggingColor) { + return Ansi.ansi().fgRed().a(kind).reset().toString(); + } else { + return kind; + } + } + + String typeAsString() { + String s; + if (type == null) { + s = "null"; + } else if (type.startsWith("java.util.concurrent")) { + s = type.substring(21); + } else if (type.startsWith("java.lang.") || type.startsWith("java.util.")) { + s = type.substring(10); + } else if (type.startsWith("org.apache.camel.support.")) { + s = type.substring(25); + } else if (type.startsWith("org.apache.camel.converter.stream.")) { + s = type.substring(34); + } else { + s = type; + } + s = "(" + s + ")"; + if (loggingColor) { + s = Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); + } + return s; + } + + String typeAndLengthAsString() { + String s; + if (type == null) { + s = "null"; + } else if (type.startsWith("java.util.concurrent")) { + s = type.substring(21); + } else if (type.startsWith("java.lang.") || type.startsWith("java.util.")) { + s = type.substring(10); + } else if (type.startsWith("org.apache.camel.support.")) { + s = type.substring(25); + } else if (type.startsWith("org.apache.camel.converter.stream.")) { + s = type.substring(34); + } else { + s = type; + } + s = "(" + s + ")"; + int l = valueLength(); + long p = position != null ? position : -1; + if (l != -1 & p != -1) { + s = s + " (pos: " + p + " length: " + l + ")"; + } else if (l != -1) { + s = s + " (length: " + l + ")"; + } else if (p != -1) { + s = s + " (pos: " + p + ")"; + } + if (loggingColor) { + s = Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); + } + return s; + } + + String mepAsKey() { + String s = key; + if (loggingColor) { + s = Ansi.ansi().fgBrightMagenta().a(Ansi.Attribute.INTENSITY_FAINT).a(s).reset().toString(); + } + return s; + } + + String exchangeIdAsValue() { + String s = value.toString(); + if (loggingColor) { + Ansi.Color color = exchangeIdColorChooser != null ? exchangeIdColorChooser.color(s) : Ansi.Color.DEFAULT; + s = Ansi.ansi().fg(color).a(s).reset().toString(); + } + return s; + } + + int valueLength() { + if (value == null) { + return -1; + } else { + return valueAsString().length(); + } + } + + } + +}
