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 d20428ae23a CAMEL-20930: camel-core - Bean DSL dev console (#14692) d20428ae23a is described below commit d20428ae23a0dd363a4c4ae3678410dde8253598 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Jul 1 21:42:01 2024 +0200 CAMEL-20930: camel-core - Bean DSL dev console (#14692) CAMEL-20930: camel-core - Bean DSL dev console Regen --- .../apache/camel/catalog/dev-consoles.properties | 1 + .../camel/catalog/dev-consoles/bean-model.json | 15 ++ components/camel-spring-xml/pom.xml | 2 +- .../apache/camel/impl/console/BeanDevConsole.java | 132 ++++++++++- .../camel/impl/console/VariablesDevConsole.java | 4 +- .../org/apache/camel/dev-console/bean-model.json | 15 ++ .../org/apache/camel/dev-console/bean-model | 2 + .../org/apache/camel/dev-consoles.properties | 7 + .../camel/model/console/BeanModelDevConsole.java | 217 +++++++++++++++++++ .../java/org/apache/camel/util/ObjectHelper.java | 2 +- .../camel/cli/connector/LocalCliConnector.java | 34 ++- .../dsl/jbang/core/commands/CamelJBangMain.java | 1 + .../jbang/core/commands/action/CamelBeanDump.java | 241 +++++++++++++++++++++ .../jbang/core/commands/process/ListVariable.java | 6 +- 14 files changed, 661 insertions(+), 18 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties index 68e2314bd3f..ecd2d10d672 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties @@ -2,6 +2,7 @@ aws-secrets aws2-s3 azure-secrets bean +bean-model blocked circuit-breaker consumer diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/bean-model.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/bean-model.json new file mode 100644 index 00000000000..76e6cae37bf --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/bean-model.json @@ -0,0 +1,15 @@ +{ + "console": { + "kind": "console", + "group": "camel", + "name": "bean-model", + "title": "Bean Model", + "description": "Displays beans from the DSL model", + "deprecated": false, + "javaType": "org.apache.camel.model.console.BeanModelDevConsole", + "groupId": "org.apache.camel", + "artifactId": "camel-core-model", + "version": "4.7.0-SNAPSHOT" + } +} + diff --git a/components/camel-spring-xml/pom.xml b/components/camel-spring-xml/pom.xml index 0ae78b19c16..bd5ceaabad1 100644 --- a/components/camel-spring-xml/pom.xml +++ b/components/camel-spring-xml/pom.xml @@ -323,7 +323,7 @@ <classifier>sources</classifier> <overWrite>true</overWrite> <includes>**/*</includes> - <excludes>**/*Configurer.java</excludes> + <excludes>**/*Configurer.java,**/console/*</excludes> <outputDirectory>target/sources/camel-core-model</outputDirectory> </artifactItem> <artifactItem> diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/BeanDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/BeanDevConsole.java index ad74cb0c332..1c0ec76b95f 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/BeanDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/BeanDevConsole.java @@ -17,11 +17,17 @@ package org.apache.camel.impl.console; import java.util.Map; +import java.util.TreeMap; import java.util.stream.Stream; +import org.apache.camel.spi.BeanIntrospection; import org.apache.camel.spi.annotations.DevConsole; +import org.apache.camel.support.PatternHelper; +import org.apache.camel.support.PluginHelper; import org.apache.camel.support.console.AbstractDevConsole; +import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; @DevConsole(name = "bean", description = "Displays Java beans from the registry") public class BeanDevConsole extends AbstractDevConsole { @@ -30,15 +36,66 @@ public class BeanDevConsole extends AbstractDevConsole { super("camel", "bean", "Bean", "Displays Java beans from the registry"); } + /** + * Filters the beans matching by name + */ + public static final String FILTER = "filter"; + + /** + * Whether to include bean properties + */ + public static final String PROPERTIES = "properties"; + + /** + * Whether to include null values + */ + public static final String NULLS = "nulls"; + + /** + * Whether to include internal Camel beans + */ + public static final String INTERNAL = "internal"; + @Override protected String doCallText(Map<String, Object> options) { + String filter = (String) options.get(FILTER); + boolean properties = "true".equals(options.getOrDefault(PROPERTIES, "true").toString()); + boolean nulls = "true".equals(options.getOrDefault(NULLS, "true").toString()); + boolean internal = "true".equals(options.getOrDefault(INTERNAL, "true").toString()); + StringBuilder sb = new StringBuilder(); + BeanIntrospection bi = PluginHelper.getBeanIntrospection(getCamelContext()); Map<String, Object> beans = getCamelContext().getRegistry().findByTypeWithName(Object.class); - Stream<String> keys = beans.keySet().stream().sorted(String::compareToIgnoreCase); + Stream<String> keys = beans.keySet().stream().filter(r -> accept(r, filter)).sorted(String::compareToIgnoreCase); keys.forEach(k -> { - String v = beans.getOrDefault(k, "<null>").getClass().getName(); - sb.append(String.format(" %s (class: %s)%n", k, v)); + Object bean = beans.get(k); + if (bean != null) { + boolean include = internal || !bean.getClass().getName().startsWith("org.apache.camel."); + if (include) { + sb.append(String.format(" %s (class: %s)%n", k, bean.getClass().getName())); + + Map<String, Object> values = new TreeMap<>(); + if (properties) { + try { + bi.getProperties(bean, values, null); + } catch (Throwable e) { + // ignore + } + values.forEach((pk, pv) -> { + if (pv == null) { + if (nulls) { + sb.append(String.format(" %s = null%n", pk)); + } + } else { + String t = pv.getClass().getName(); + sb.append(String.format(" %s (%s) = %s%n", pk, t, pv)); + } + }); + } + } + } + sb.append("\n"); }); return sb.toString(); @@ -46,17 +103,78 @@ public class BeanDevConsole extends AbstractDevConsole { @Override protected JsonObject doCallJson(Map<String, Object> options) { - JsonObject root = new JsonObject(); + String filter = (String) options.get(FILTER); + boolean properties = "true".equals(options.getOrDefault(PROPERTIES, "true").toString()); + boolean nulls = "true".equals(options.getOrDefault(NULLS, "true").toString()); + boolean internal = "true".equals(options.getOrDefault(INTERNAL, "true").toString()); + JsonObject root = new JsonObject(); JsonObject jo = new JsonObject(); root.put("beans", jo); + + BeanIntrospection bi = PluginHelper.getBeanIntrospection(getCamelContext()); Map<String, Object> beans = getCamelContext().getRegistry().findByTypeWithName(Object.class); - Stream<String> keys = beans.keySet().stream().sorted(String::compareToIgnoreCase); + Stream<String> keys = beans.keySet().stream().filter(r -> accept(r, filter)).sorted(String::compareToIgnoreCase); + keys.forEach(k -> { - String v = beans.getOrDefault(k, "null").getClass().getName(); - beans.put(k, v); + Object bean = beans.get(k); + if (bean != null) { + boolean include = internal || !bean.getClass().getName().startsWith("org.apache.camel."); + if (include) { + Map<String, Object> values = new TreeMap<>(); + if (properties) { + try { + bi.getProperties(bean, values, null); + } catch (Throwable e) { + // ignore + } + } + JsonObject jb = new JsonObject(); + jb.put("name", k); + jb.put("type", bean.getClass().getName()); + jo.put(k, jb); + + if (!values.isEmpty()) { + JsonArray arr = new JsonArray(); + values.forEach((pk, pv) -> { + Object value = pv; + String type = pv != null ? pv.getClass().getName() : null; + if (type != null) { + value = Jsoner.trySerialize(pv); + if (value == null) { + // cannot serialize so escape + value = Jsoner.escape(pv.toString()); + } else { + // okay so use the value as-s + value = pv; + } + } + JsonObject jp = new JsonObject(); + jp.put("name", pk); + if (type != null) { + jp.put("type", type); + } + jp.put("value", value); + boolean accept = value != null || nulls; + if (accept) { + arr.add(jp); + } + }); + jb.put("properties", arr); + } + } + } }); return root; } + + private static boolean accept(String name, String filter) { + if (filter == null || filter.isBlank()) { + return true; + } + + return PatternHelper.matchPattern(name, filter); + } + } diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/VariablesDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/VariablesDevConsole.java index 95260395237..bb36068f931 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/VariablesDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/VariablesDevConsole.java @@ -76,10 +76,10 @@ public class VariablesDevConsole extends AbstractDevConsole { String t = v != null ? v.getClass().getName() : null; JsonObject e = new JsonObject(); e.put("key", k); - e.put("value", v); if (t != null) { - e.put("className", t); + e.put("type", t); } + e.put("value", v); arr.add(e); } return arr; diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/dev-console/bean-model.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/dev-console/bean-model.json new file mode 100644 index 00000000000..76e6cae37bf --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/dev-console/bean-model.json @@ -0,0 +1,15 @@ +{ + "console": { + "kind": "console", + "group": "camel", + "name": "bean-model", + "title": "Bean Model", + "description": "Displays beans from the DSL model", + "deprecated": false, + "javaType": "org.apache.camel.model.console.BeanModelDevConsole", + "groupId": "org.apache.camel", + "artifactId": "camel-core-model", + "version": "4.7.0-SNAPSHOT" + } +} + diff --git a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-console/bean-model b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-console/bean-model new file mode 100644 index 00000000000..1e9e05a4ed1 --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-console/bean-model @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.model.console.BeanModelDevConsole diff --git a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties new file mode 100644 index 00000000000..a3834de6926 --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +dev-consoles=bean-model +groupId=org.apache.camel +artifactId=camel-core-model +version=4.7.0-SNAPSHOT +projectName=Camel :: Core Model +projectDescription=Camel model diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/console/BeanModelDevConsole.java b/core/camel-core-model/src/main/java/org/apache/camel/model/console/BeanModelDevConsole.java new file mode 100644 index 00000000000..fc537b718fb --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/console/BeanModelDevConsole.java @@ -0,0 +1,217 @@ +/* + * 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.model.console; + +import java.util.Map; +import java.util.TreeMap; + +import org.apache.camel.model.BeanFactoryDefinition; +import org.apache.camel.model.Model; +import org.apache.camel.spi.BeanIntrospection; +import org.apache.camel.spi.annotations.DevConsole; +import org.apache.camel.support.PatternHelper; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.console.AbstractDevConsole; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; + +@DevConsole(name = "bean-model", description = "Displays beans from the DSL model") +public class BeanModelDevConsole extends AbstractDevConsole { + + /** + * Filters the beans matching by name + */ + public static final String FILTER = "filter"; + + /** + * Whether to include bean properties + */ + public static final String PROPERTIES = "properties"; + + /** + * Whether to include null values + */ + public static final String NULLS = "nulls"; + + public BeanModelDevConsole() { + super("camel", "bean-model", "Bean Model", "Displays beans from the DSL model"); + } + + @Override + protected String doCallText(Map<String, Object> options) { + String filter = (String) options.get(FILTER); + boolean properties = "true".equals(options.getOrDefault(PROPERTIES, "true")); + boolean nulls = "true".equals(options.getOrDefault(NULLS, "true")); + + StringBuilder sb = new StringBuilder(); + + BeanIntrospection bi = PluginHelper.getBeanIntrospection(getCamelContext()); + Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class); + if (model != null) { + for (BeanFactoryDefinition<?> b : model.getCustomBeans()) { + String name = b.getName(); + if (!accept(name, filter)) { + continue; + } + + Map<String, Object> values = new TreeMap<>(); + Object target = getCamelContext().getRegistry().lookupByName(name); + if (target != null && properties) { + try { + bi.getProperties(target, values, null); + } catch (Throwable e) { + // ignore + } + } + sb.append(String.format(" %s (%s)%n", b.getName(), b.getType())); + if (properties && b.getProperties() != null) { + b.getProperties().forEach((k, v) -> { + Object rv = values.get(k); + String type; + if (rv == null) { + if (nulls) { + sb.append(String.format(" %s = null%n", k)); + + } + } else { + type = rv.getClass().getName(); + sb.append(String.format(" %s = %s (type:%s)%n", k, rv, type)); + } + }); + } + sb.append("\n"); + } + } + + return sb.toString(); + } + + @Override + protected JsonObject doCallJson(Map<String, Object> options) { + String filter = (String) options.get(FILTER); + boolean properties = "true".equals(options.getOrDefault(PROPERTIES, "true")); + boolean nulls = "true".equals(options.getOrDefault(NULLS, "true")); + + JsonObject root = new JsonObject(); + + JsonObject jo = new JsonObject(); + root.put("beans", jo); + + BeanIntrospection bi = PluginHelper.getBeanIntrospection(getCamelContext()); + Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class); + if (model != null) { + for (BeanFactoryDefinition<?> b : model.getCustomBeans()) { + String name = b.getName(); + if (!accept(name, filter)) { + continue; + } + + Map<String, Object> values = new TreeMap<>(); + Object target = getCamelContext().getRegistry().lookupByName(name); + if (target != null && properties) { + try { + bi.getProperties(target, values, null); + } catch (Throwable e) { + // ignore + } + } + JsonObject jb = new JsonObject(); + jo.put(b.getName(), jb); + jb.put("name", b.getName()); + jb.put("type", b.getType()); + if (b.getInitMethod() != null) { + jb.put("initMethod", b.getInitMethod()); + } + if (b.getDestroyMethod() != null) { + jb.put("destroyMethod", b.getDestroyMethod()); + } + if (b.getBuilderClass() != null) { + jb.put("builderClass", b.getBuilderClass()); + } + if (b.getBuilderMethod() != null) { + jb.put("builderMethod", b.getBuilderMethod()); + } + if (b.getFactoryBean() != null) { + jb.put("factoryBean", b.getFactoryBean()); + } + if (b.getFactoryMethod() != null) { + jb.put("factoryMethod", b.getFactoryMethod()); + } + if (b.getProperties() != null) { + JsonArray arr = new JsonArray(); + b.getProperties().forEach((k, v) -> { + Object rv = values.get(k); + String type = rv != null ? rv.getClass().getName() : null; + JsonObject jp = new JsonObject(); + jp.put("name", k); + if (type != null) { + jp.put("type", type); + } + jp.put("value", v); + boolean accept = v != null || nulls; + if (accept) { + arr.add(jp); + } + }); + if (!arr.isEmpty()) { + jb.put("modelProperties", arr); + } + JsonArray arr2 = new JsonArray(); + b.getProperties().forEach((k, v) -> { + Object rv = values.get(k); + Object value = rv; + String type = rv != null ? rv.getClass().getName() : null; + if (type != null) { + value = Jsoner.trySerialize(rv); + if (value == null) { + // cannot serialize so escape + value = Jsoner.escape(rv.toString()); + } else { + // okay so use the value as-s + value = rv; + } + } + JsonObject jp = new JsonObject(); + jp.put("name", k); + if (type != null) { + jp.put("type", type); + } + jp.put("value", value); + boolean accept = value != null || nulls; + if (accept) { + arr2.add(jp); + } + }); + if (!arr2.isEmpty()) { + jb.put("properties", arr2); + } + } + } + } + return root; + } + + private static boolean accept(String name, String filter) { + if (filter == null || filter.isBlank()) { + return true; + } + + return PatternHelper.matchPattern(name, filter); + } + +} diff --git a/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java index bc4f3da3b6e..3227ce736e0 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java @@ -495,7 +495,7 @@ public final class ObjectHelper { return Object[].class; } else if ("java.lang.String[]".equals(name) || "String[]".equals(name)) { return String[].class; - // and these is common as well + // and these are common as well } else if ("java.lang.String".equals(name) || "String".equals(name)) { return String.class; } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name)) { 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 f288cc1f174..d230f7fd06e 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 @@ -263,15 +263,16 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C doActionSendTask(root); } else if ("transform".equals(action)) { doActionTransformTask(root); + } else if ("bean".equals(action)) { + doActionBeanTask(root); } - - // action done so delete file - FileUtil.deleteFile(actionFile); - } catch (Exception e) { // ignore LOG.debug("Error executing action file: {} due to: {}. This exception is ignored.", actionFile, e.getMessage(), e); + } finally { + // action done so delete file + FileUtil.deleteFile(actionFile); } } @@ -694,6 +695,31 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C } } + private void doActionBeanTask(JsonObject root) throws IOException { + String filter = root.getStringOrDefault("filter", ""); + String properties = root.getStringOrDefault("properties", "true"); + String nulls = root.getStringOrDefault("nulls", "true"); + String internal = root.getStringOrDefault("internal", "false"); + + Map<String, Object> options = Map.of("filter", filter, "properties", properties, "nulls", nulls, "internal", internal); + + JsonObject answer = new JsonObject(); + DevConsole dc1 = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class) + .resolveById("bean"); + DevConsole dc2 = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class) + .resolveById("bean-model"); + if (dc1 != null) { + JsonObject json = (JsonObject) dc1.call(DevConsole.MediaType.JSON, options); + answer.put("beans", json.getMap("beans")); + } + if (dc2 != null) { + JsonObject json = (JsonObject) dc2.call(DevConsole.MediaType.JSON, options); + answer.put("bean-models", json.getMap("beans")); + } + LOG.trace("Updating output file: {}", outputFile); + IOHelper.writeText(answer.toJson(), outputFile); + } + private void doActionResetStatsTask() throws Exception { ManagedCamelContext mcc = camelContext.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); if (mcc != null) { 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 7e295c9c2c8..31a64f611fc 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 @@ -93,6 +93,7 @@ public class CamelJBangMain implements Callable<Integer> { .addSubcommand("event", new CommandLine(new ListEvent(main))) .addSubcommand("inflight", new CommandLine(new ListInflight(main))) .addSubcommand("blocked", new CommandLine(new ListBlocked(main))) + .addSubcommand("bean", new CommandLine(new CamelBeanDump(main))) .addSubcommand("route-controller", new CommandLine(new RouteControllerAction(main))) .addSubcommand("transformer", new CommandLine(new ListTransformer(main))) .addSubcommand("circuit-breaker", new CommandLine(new ListCircuitBreaker(main))) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java new file mode 100644 index 00000000000..7c46cc313e2 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java @@ -0,0 +1,241 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +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.commands.CamelJBangMain; +import org.apache.camel.util.FileUtil; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Command(name = "bean", description = "List beans in a running Camel integration", sortOptions = false) +public class CamelBeanDump extends ActionBaseCommand { + + public static class NameTypeCompletionCandidates implements Iterable<String> { + + public NameTypeCompletionCandidates() { + } + + @Override + public Iterator<String> iterator() { + return List.of("name", "type").iterator(); + } + + } + + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1") + String name = "*"; + + @CommandLine.Option(names = { "--sort" }, completionCandidates = NameTypeCompletionCandidates.class, + description = "Sort by name or type", defaultValue = "name") + String sort; + + @CommandLine.Option(names = { "--filter" }, + description = "Filter beans names (use all to include all beans)", defaultValue = "all") + String filter; + + @CommandLine.Option(names = { "--properties" }, + description = "Show bean properties", defaultValue = "true") + boolean properties; + + @CommandLine.Option(names = { "--nulls" }, + description = "Include null values", defaultValue = "true") + boolean nulls; + + @CommandLine.Option(names = { "--internal" }, + description = "Include internal Camel beans", defaultValue = "false") + boolean internal; + + @CommandLine.Option(names = { "--dsl" }, + description = "Include only beans from YAML or XML DSL", defaultValue = "false") + boolean dsl; + + private volatile long pid; + + public CamelBeanDump(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + List<Row> rows = new ArrayList<>(); + + List<Long> pids = findPids(name); + if (pids.isEmpty()) { + return 0; + } else if (pids.size() > 1) { + printer().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", "bean"); + if (!"all".equals(filter)) { + root.put("filter", filter); + } + root.put("properties", properties); + root.put("nulls", nulls); + root.put("internal", internal); + File f = getActionFile(Long.toString(pid)); + try { + IOHelper.writeText(root.toJson(), f); + } catch (Exception e) { + // ignore + } + + JsonObject jo = waitForOutputFile(outputFile); + + if (jo != null) { + JsonObject beans; + if (dsl) { + beans = (JsonObject) jo.get("bean-models"); + } else { + beans = (JsonObject) jo.get("beans"); + } + for (String name : beans.keySet()) { + JsonObject jt = (JsonObject) beans.get(name); + Row row = new Row(); + row.name = jt.getString("name"); + row.type = jt.getString("type"); + JsonArray arr = jt.getCollection("properties"); + JsonArray arr2 = jt.getCollection("modelProperties"); + if (arr != null) { + row.properties = new ArrayList<>(); + for (int i = 0; i < arr.size(); i++) { + PropertyRow pr = new PropertyRow(); + row.properties.add(pr); + JsonObject p = (JsonObject) arr.get(i); + pr.name = p.getString("name"); + pr.type = p.getString("type"); + pr.value = p.get("value"); + if (arr2 != null) { + JsonObject p2 = (JsonObject) arr2.get(i); + if (p2 != null) { + pr.configValue = p2.getString("value"); + } + } + } + } + rows.add(row); + } + } else { + printer().println("Response from running Camel with PID " + pid + " not received within 5 seconds"); + return 1; + } + + // sort rows + rows.sort(this::sortRow); + if (properties) { + for (Row row : rows) { + String line = "BEAN: " + row.name + " (" + row.type + "):"; + printer().println(line); + printer().println("-".repeat(line.length())); + if (row.properties != null) { + propertiesTable(row.properties); + } + printer().println(); + } + } else { + singleTable(rows); + } + + // delete output file after use + FileUtil.deleteFile(outputFile); + + return 0; + } + + protected void singleTable(List<Row> rows) { + printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + new Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(60, OverflowBehaviour.ELLIPSIS_RIGHT) + .with(r -> r.name), + new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).maxWidth(100, OverflowBehaviour.CLIP_LEFT) + .with(r -> r.type)))); + } + + protected void propertiesTable(List<PropertyRow> rows) { + printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + new Column().header("PROPERTY").dataAlign(HorizontalAlign.LEFT).maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT) + .with(r -> r.name), + new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT) + .with(r -> r.type), + new Column().header("CONFIGURATION").visible(dsl).dataAlign(HorizontalAlign.LEFT) + .maxWidth(80, OverflowBehaviour.NEWLINE) + .with(r -> r.configValue), + new Column().header("VALUE").dataAlign(HorizontalAlign.LEFT).maxWidth(80, OverflowBehaviour.NEWLINE) + .with(this::getValue)))); + } + + private String getValue(PropertyRow r) { + if (r.value != null) { + return r.value.toString(); + } + return "null"; + } + + protected int sortRow(Row o1, Row o2) { + String s = sort; + int negate = 1; + if (s.startsWith("-")) { + s = s.substring(1); + negate = -1; + } + switch (s) { + case "name": + return o1.name.compareToIgnoreCase(o2.name) * negate; + default: + return 0; + } + } + + protected JsonObject waitForOutputFile(File outputFile) { + return getJsonObject(outputFile); + } + + private static class Row { + String name; + String type; + List<PropertyRow> properties; + } + + private static class PropertyRow { + String name; + String type; + Object value; + String configValue; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariable.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariable.java index c5910e354aa..f19fa750447 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariable.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariable.java @@ -89,7 +89,7 @@ public class ListVariable extends ProcessWatchCommand { JsonObject jo = (JsonObject) arr.get(i); row.id = id; row.key = jo.getString("key"); - row.className = jo.getString("className"); + row.type = jo.getString("type"); row.value = jo.get("value"); rows.add(row); } @@ -108,7 +108,7 @@ public class ListVariable extends ProcessWatchCommand { .with(r -> r.name), new Column().header("REPOSITORY").headerAlign(HorizontalAlign.CENTER).with(r -> r.id), new Column().header("TYPE").headerAlign(HorizontalAlign.CENTER) - .maxWidth(40, OverflowBehaviour.ELLIPSIS_LEFT).with(r -> r.className), + .maxWidth(40, OverflowBehaviour.ELLIPSIS_LEFT).with(r -> r.type), new Column().header("KEY").dataAlign(HorizontalAlign.LEFT).maxWidth(50, OverflowBehaviour.ELLIPSIS_RIGHT) .with(r -> r.key), new Column().header("VALUE").headerAlign(HorizontalAlign.RIGHT).maxWidth(80, OverflowBehaviour.NEWLINE) @@ -154,7 +154,7 @@ public class ListVariable extends ProcessWatchCommand { String name; String id; String key; - String className; + String type; Object value; Row copy() {