This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch worktree-tui5 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8d89bc3a1cb2bb37d2516e400eb0c0eef26d7fc8 Author: Claus Ibsen <[email protected]> AuthorDate: Wed May 20 15:11:36 2026 +0200 CAMEL-23566: camel-jbang - Source examples from camel-jbang-examples repository Replace the 3 hardcoded built-in examples with a catalog-driven system sourcing examples from the apache/camel-jbang-examples repository. - Add ExampleHelper with catalog loading, filtering by name/tag/level, bundled extraction (with subdirectory support), and GitHub fetch - Refactor Run.java --example/--example-list to use the catalog JSON - --example-list now shows NAME, LEVEL, DESCRIPTION, SOURCE columns and supports filtering (e.g. --example-list beginner, --example-list ai) - Bundled examples (7) work offline, others fetch from GitHub on demand - Add -Psync-example-catalog Maven profile to download latest catalog - Bundle 7 curated examples with READMEs: timer-log, rest-api, cron-log, circuit-breaker, groovy, routes, xslt Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../pages/jbang-commands/camel-jbang-debug.adoc | 4 +- .../ROOT/pages/jbang-commands/camel-jbang-run.adoc | 4 +- dsl/camel-jbang/camel-jbang-core/pom.xml | 30 ++ .../META-INF/camel-jbang-commands-metadata.json | 4 +- .../apache/camel/dsl/jbang/core/commands/Run.java | 153 ++++++-- .../camel/dsl/jbang/core/common/ExampleHelper.java | 164 ++++++++ .../examples/camel-jbang-example-catalog.json | 436 +++++++++++++++++++++ .../resources/examples/circuit-breaker/README.adoc | 61 +++ .../examples/circuit-breaker/route.camel.yaml | 43 ++ .../src/main/resources/examples/cron-log.yaml | 10 - .../main/resources/examples/cron-log/README.adoc | 7 + .../examples/cron-log/cron-log.camel.yaml | 27 ++ .../src/main/resources/examples/groovy/README.adoc | 76 ++++ .../examples/groovy/application.properties | 17 + .../resources/examples/groovy/groovy.camel.yaml | 49 +++ .../src/main/resources/examples/rest-api.yaml | 21 - .../main/resources/examples/rest-api/README.adoc | 12 + .../examples/rest-api/rest-api.camel.yaml | 38 ++ .../main/resources/examples/routes/Greeter.java | 36 ++ .../src/main/resources/examples/routes/README.adoc | 78 ++++ .../src/main/resources/examples/routes/beans.yaml | 22 ++ .../resources/examples/routes/routes.camel.yaml | 29 ++ .../src/main/resources/examples/timer-log.yaml | 10 - .../main/resources/examples/timer-log/README.adoc | 7 + .../examples/timer-log/timer-log.camel.yaml | 27 ++ .../src/main/resources/examples/xslt/README.adoc | 66 ++++ .../resources/examples/xslt/consumer.camel.yaml | 30 ++ .../main/resources/examples/xslt/input/account.xml | 27 ++ .../main/resources/examples/xslt/stylesheet.xsl | 29 ++ .../camel/dsl/jbang/core/commands/RunTest.java | 41 +- .../dsl/jbang/core/common/ExampleHelperTest.java | 155 ++++++++ 31 files changed, 1622 insertions(+), 91 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc index 2ba7c40779cd..5769d9aa2675 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc @@ -31,8 +31,8 @@ camel debug [options] | `--dep,--dependency` | Add additional dependencies | | List | `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean | `--empty` | Run an empty Camel without loading source files | false | boolean -| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String -| `--example-list` | List available built-in examples | | boolean +| `--example` | Run an example by name. Use --example-list to show available examples. | | String +| `--example-list` | List available examples. Optionally filter by keyword (e.g., --example-list ai). | | String | `--exclude` | Exclude files by name or pattern | | List | `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean | `--gav` | The Maven group:artifact:version (used during exporting) | | String diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc index 936fd1385e3a..888de5459839 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc @@ -29,8 +29,8 @@ camel run [options] | `--dep,--dependency` | Add additional dependencies | | List | `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean | `--empty` | Run an empty Camel without loading source files | false | boolean -| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String -| `--example-list` | List available built-in examples | | boolean +| `--example` | Run an example by name. Use --example-list to show available examples. | | String +| `--example-list` | List available examples. Optionally filter by keyword (e.g., --example-list ai). | | String | `--exclude` | Exclude files by name or pattern | | List | `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean | `--gav` | The Maven group:artifact:version (used during exporting) | | String diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml index 194fce245ab7..a59b10bcccbc 100644 --- a/dsl/camel-jbang/camel-jbang-core/pom.xml +++ b/dsl/camel-jbang/camel-jbang-core/pom.xml @@ -250,4 +250,34 @@ </plugins> </build> + <profiles> + <profile> + <id>sync-example-catalog</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>download-example-catalog</id> + <phase>generate-resources</phase> + <goals> + <goal>run</goal> + </goals> + <configuration> + <target> + <get src="https://raw.githubusercontent.com/apache/camel-jbang-examples/main/camel-jbang-example-catalog.json" + dest="${project.basedir}/src/main/resources/examples/camel-jbang-example-catalog.json" + usetimestamp="true"/> + </target> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + </project> diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json index 055c7288eedb..c774787e2a4d 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json @@ -5,7 +5,7 @@ { "name": "cmd", "fullName": "cmd", "description": "Performs commands in the running Camel integrations, such as start\/stop route, or change logging levels.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "browse", "fullName": "cmd browse", "description": "Browse pending messages on endpoints [...] { "name": "completion", "fullName": "completion", "description": "Generate completion script for bash\/zsh", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "config", "fullName": "config", "description": "Get and set user configuration values", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "config get", "description": "Display user configuration value", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...] - { "name": "debug", "fullName": "debug", "description": "Debug local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "To [...] + { "name": "debug", "fullName": "debug", "description": "Debug local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "To [...] { "name": "dependency", "fullName": "dependency", "description": "Displays all Camel dependencies required to run", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "copy", "fullName": "dependency copy", "description": "Copies all Camel dependencies required to run to a specific directory", "sourc [...] { "name": "dirty", "fullName": "dirty", "description": "Check if there are dirty files from previous Camel runs that did not terminate gracefully", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty", "options": [ { "names": "--clean", "description": "Clean dirty files which are no longer in use", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", " [...] { "name": "doc", "fullName": "doc", "description": "Shows documentation for kamelet, component, and other Camel resources", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ { "names": "--camel-version", "description": "To use a different Camel version than the default version", "javaType": "java.lang.String", "type": "string" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet [...] @@ -23,7 +23,7 @@ { "name": "nano", "fullName": "nano", "description": "Nano editor to edit file", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Nano", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "plugin", "fullName": "plugin", "description": "Manage plugins that add sub-commands to this CLI", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add", "fullName": "plugin add", "description": "Add new plugin", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...] { "name": "ps", "fullName": "ps", "description": "List running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--pid", "description": "List only pid in the output", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": [...] - { "name": "run", "fullName": "run", "description": "Run as local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run", "options": [ { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "To wait for run in background to startup successfully, before returning", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { [...] + { "name": "run", "fullName": "run", "description": "Run as local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run", "options": [ { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "To wait for run in background to startup successfully, before returning", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { [...] { "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX or SPDX SBOM for a specific project", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ { "names": "--build-property", "description": "Maven build properties, ex. --build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { "names": "--camel-spring-boot-version", "description": "Camel version to use with Spring Boot", "javaType": "java.lang.String", "type" [...] { "name": "script", "fullName": "script", "description": "Run Camel integration as shell script for terminal scripting", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names": "--logging", "description": "Can be used to turn on logging (logs to file in <user home>\/.camel directory)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--logging-level", "description": "Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...] { "name": "shell", "fullName": "shell", "description": "Interactive Camel JBang shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, 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 9876387f3ac8..8916144d20bf 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 @@ -44,6 +44,7 @@ import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper; +import org.apache.camel.dsl.jbang.core.common.ExampleHelper; import org.apache.camel.dsl.jbang.core.common.JavaVersionCompletionCandidates; import org.apache.camel.dsl.jbang.core.common.LauncherHelper; import org.apache.camel.dsl.jbang.core.common.LoggingLevelCompletionCandidates; @@ -338,13 +339,14 @@ public class Run extends CamelCommand { boolean skipPlugins; @Option(names = { "--example" }, - description = "Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples.", + description = "Run an example by name. Use --example-list to show available examples.", arity = "0..1", fallbackValue = "") String example; @Option(names = { "--example-list" }, - description = "List available built-in examples") - boolean exampleList; + description = "List available examples. Optionally filter by keyword (e.g., --example-list ai).", + arity = "0..1", fallbackValue = "") + String exampleFilter; public Run(CamelJBangMain main) { super(main); @@ -366,8 +368,8 @@ public class Run extends CamelCommand { @Override public Integer doCall() throws Exception { // handle --example - if (exampleList || (example != null && example.isEmpty())) { - return listExamples(); + if (exampleFilter != null || (example != null && example.isEmpty())) { + return listExamples(exampleFilter); } if (example != null) { return runExample(); @@ -380,26 +382,56 @@ public class Run extends CamelCommand { return run(); } - private int listExamples() { - printer().println("Available built-in examples:"); + private int listExamples(String filter) { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + if (catalog.isEmpty()) { + printer().printErr("No example catalog found."); + return 1; + } + + List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, filter); + if (filtered.isEmpty()) { + printer().printErr("No examples matching: " + filter); + return 1; + } + + if (filter != null && !filter.isEmpty()) { + printer().println("Examples matching '" + filter + "':"); + } else { + printer().println("Available examples:"); + } printer().println(); - printer().printf(" %-20s %s%n", "timer-log", "Simple timer that logs messages every second"); - printer().printf(" %-20s %s%n", "rest-api", "REST API with hello endpoints"); - printer().printf(" %-20s %s%n", "cron-log", "Scheduled task that logs every 5 seconds"); + printer().printf(" %-30s %-14s %-50s %s%n", "NAME", "LEVEL", "DESCRIPTION", "SOURCE"); + printer().printf(" %-30s %-14s %-50s %s%n", "----", "-----", "-----------", "------"); + for (JsonObject entry : filtered) { + String eName = entry.getString("name"); + String level = entry.getString("level"); + if (level == null) { + level = ""; + } + String desc = entry.getString("description"); + if (desc.length() > 50) { + desc = desc.substring(0, 47) + "..."; + } + String source = ExampleHelper.isBundled(entry) ? "bundled" : "online"; + if (ExampleHelper.requiresDocker(entry)) { + source += ", docker"; + } + printer().printf(" %-30s %-14s %-50s %s%n", eName, level, desc, source); + } printer().println(); printer().println("Usage: camel run --example <name>"); printer().println(" camel run --example <name> --dev"); return 0; } - private static final List<String> EXAMPLE_NAMES = List.of("timer-log", "rest-api", "cron-log"); - private int runExample() throws Exception { - String resourcePath = "examples/" + example + ".yaml"; - InputStream is = Run.class.getClassLoader().getResourceAsStream(resourcePath); - if (is == null) { - List<String> suggestions - = SuggestSimilarHelper.didYouMean(EXAMPLE_NAMES, example); + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, example); + + if (entry == null) { + List<String> names = ExampleHelper.getExampleNames(catalog); + List<String> suggestions = SuggestSimilarHelper.didYouMean(names, example); if (!suggestions.isEmpty()) { printer().printErr("Unknown example: " + example + ". Did you mean? " + String.join(", ", suggestions)); } else { @@ -409,29 +441,80 @@ public class Run extends CamelCommand { return 1; } - // extract example to a temp file and run it - Path tempDir = Files.createTempDirectory("camel-example-"); - Path exampleFile = tempDir.resolve(example + ".yaml"); + if (ExampleHelper.isBundled(entry)) { + return runBundledExample(entry); + } else { + return runGithubExample(entry); + } + } + + private int runBundledExample(JsonObject entry) throws Exception { + String eName = entry.getString("name"); + Path tempDir = ExampleHelper.extractBundledExample(entry); + List<String> exampleFiles = ExampleHelper.getFiles(entry); + + printer().println("Running example: " + eName); + for (String f : exampleFiles) { + files.add(tempDir.resolve(f).toString()); + } + if ("CamelJBang".equals(name)) { + name = eName; + } + + if (!exportRun) { + printConfigurationValues("Running integration with the following configuration:"); + } + return run(); + } + + private int runGithubExample(JsonObject entry) throws Exception { + String eName = entry.getString("name"); + String url = ExampleHelper.getGithubUrl(entry); + + printer().println("Fetching example from GitHub: " + eName); + if (ExampleHelper.requiresDocker(entry)) { + printer().println("Note: this example requires Docker/Podman"); + } + + StringJoiner routes = new StringJoiner(","); + StringJoiner kamelets = new StringJoiner(","); + StringJoiner properties = new StringJoiner(","); try { - String content = IOHelper.loadText(is); - IOHelper.close(is); - Files.writeString(exampleFile, content); + fetchGithubUrls(url, routes, kamelets, properties); + } catch (Exception e) { + printer().printErr("Failed to fetch example from GitHub: " + e.getMessage()); + printer().printErr("This example requires an internet connection."); + return 1; + } - printer().println("Running example: " + example); - files.add(exampleFile.toString()); - if ("CamelJBang".equals(name)) { - name = example; - } + if (routes.length() == 0 && kamelets.length() == 0 && properties.length() == 0) { + printer().printErr("No files found for example: " + eName); + return 1; + } - if (!exportRun) { - printConfigurationValues("Running integration with the following configuration:"); + if (routes.length() > 0) { + for (String r : routes.toString().split(",")) { + files.add(r); } - return run(); - } finally { - // clean up temp files on JVM exit - exampleFile.toFile().deleteOnExit(); - tempDir.toFile().deleteOnExit(); } + if (kamelets.length() > 0) { + for (String k : kamelets.toString().split(",")) { + files.add(k); + } + } + if (properties.length() > 0) { + for (String p : properties.toString().split(",")) { + files.add(p); + } + } + if ("CamelJBang".equals(name)) { + name = eName; + } + + if (!exportRun) { + printConfigurationValues("Running integration with the following configuration:"); + } + return run(); } public Integer runExport() throws Exception { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java new file mode 100644 index 000000000000..3ce60b684c1e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java @@ -0,0 +1,164 @@ +/* + * 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.common; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; + +public final class ExampleHelper { + + private static final String CATALOG_RESOURCE = "examples/camel-jbang-example-catalog.json"; + private static final String GITHUB_EXAMPLES_URL + = "https://github.com/apache/camel-jbang-examples/tree/main/"; + + private ExampleHelper() { + } + + public static List<JsonObject> loadCatalog() { + List<JsonObject> catalog = new ArrayList<>(); + try (InputStream is = ExampleHelper.class.getClassLoader().getResourceAsStream(CATALOG_RESOURCE)) { + if (is == null) { + return catalog; + } + String json = IOHelper.loadText(is); + JsonArray array = (JsonArray) Jsoner.deserialize(json); + for (Object item : array) { + catalog.add((JsonObject) item); + } + } catch (Exception e) { + // ignore + } + return catalog; + } + + public static JsonObject findExample(List<JsonObject> catalog, String name) { + for (JsonObject entry : catalog) { + if (name.equals(entry.getString("name"))) { + return entry; + } + } + return null; + } + + public static List<String> getExampleNames(List<JsonObject> catalog) { + List<String> names = new ArrayList<>(); + for (JsonObject entry : catalog) { + names.add(entry.getString("name")); + } + return names; + } + + public static List<JsonObject> filterExamples(List<JsonObject> catalog, String filter) { + if (filter == null || filter.isEmpty()) { + return catalog; + } + String lowerFilter = filter.toLowerCase(); + List<JsonObject> result = new ArrayList<>(); + for (JsonObject entry : catalog) { + if (matches(entry, lowerFilter)) { + result.add(entry); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private static boolean matches(JsonObject entry, String filter) { + String name = entry.getString("name"); + if (name != null && name.toLowerCase().contains(filter)) { + return true; + } + String title = entry.getString("title"); + if (title != null && title.toLowerCase().contains(filter)) { + return true; + } + String desc = entry.getString("description"); + if (desc != null && desc.toLowerCase().contains(filter)) { + return true; + } + String level = entry.getString("level"); + if (level != null && level.toLowerCase().contains(filter)) { + return true; + } + Collection<String> tags = (Collection<String>) entry.get("tags"); + if (tags != null) { + for (String tag : tags) { + if (tag.toLowerCase().contains(filter)) { + return true; + } + } + } + return false; + } + + public static boolean isBundled(JsonObject entry) { + Boolean bundled = entry.getBoolean("bundled"); + return bundled != null && bundled; + } + + public static boolean requiresDocker(JsonObject entry) { + Boolean docker = entry.getBoolean("requiresDocker"); + return docker != null && docker; + } + + @SuppressWarnings("unchecked") + public static List<String> getFiles(JsonObject entry) { + Collection<String> files = (Collection<String>) entry.get("files"); + if (files == null) { + return List.of(); + } + return new ArrayList<>(files); + } + + public static Path extractBundledExample(JsonObject entry) throws Exception { + String name = entry.getString("name"); + List<String> fileNames = getFiles(entry); + Path tempDir = Files.createTempDirectory("camel-example-"); + + for (String fileName : fileNames) { + String resourcePath = "examples/" + name + "/" + fileName; + try (InputStream is = ExampleHelper.class.getClassLoader().getResourceAsStream(resourcePath)) { + if (is != null) { + String content = IOHelper.loadText(is); + Path targetFile = tempDir.resolve(fileName); + // create parent dirs for nested files like input/account.xml + Files.createDirectories(targetFile.getParent()); + Files.writeString(targetFile, content); + targetFile.toFile().deleteOnExit(); + targetFile.getParent().toFile().deleteOnExit(); + } + } + } + + tempDir.toFile().deleteOnExit(); + return tempDir; + } + + public static String getGithubUrl(JsonObject entry) { + return GITHUB_EXAMPLES_URL + entry.getString("name"); + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json new file mode 100644 index 000000000000..cdd5c70d7a03 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json @@ -0,0 +1,436 @@ +[ + { + "name": "artemis", + "title": "Apache ActiveMQ Artemis", + "description": "Setup connection factory to a remote Apache ActiveMQ Artemis messaging broker", + "level": "intermediate", + "tags": [ + "messaging", + "jms", + "artemis" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "consumer.camel.yaml", + "producer.camel.yaml" + ] + }, + { + "name": "aws/aws-s3-event-based", + "title": "AWS S3 CDC", + "description": "Consume S3 events using EventBridge and SQS for change data capture", + "level": "advanced", + "tags": [ + "aws", + "s3", + "sqs", + "eventbridge", + "cloud" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "aws-s3-cdc-log.camel.yaml", + "example-file.txt", + "policy-queue.json", + "terraform/main.tf" + ] + }, + { + "name": "aws/aws-sqs", + "title": "AWS SQS Sink", + "description": "Push messages to AWS SQS queue via HTTP service", + "level": "advanced", + "tags": [ + "aws", + "sqs", + "http", + "cloud" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "http-to-aws-sqs.camel.yaml" + ] + }, + { + "name": "circuit-breaker", + "title": "Circuit Breaker", + "description": "Use the circuit breaker EIP for fault tolerance", + "level": "beginner", + "tags": [ + "eip", + "resilience" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "route.camel.yaml" + ] + }, + { + "name": "cron-log", + "title": "Cron Log", + "description": "Scheduled task that logs every 5 seconds", + "level": "beginner", + "tags": [ + "beginner", + "timer", + "cron", + "log" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "cron-log.camel.yaml" + ] + }, + { + "name": "docling-langchain4j-rag", + "title": "Document Analysis with Docling and LangChain4j RAG", + "description": "RAG workflow combining Docling document conversion with LangChain4j and Ollama", + "level": "advanced", + "tags": [ + "ai", + "rag", + "langchain4j", + "docling" + ], + "bundled": false, + "requiresDocker": true, + "files": [ + "README.adoc", + "application.properties", + "compose.yaml", + "docling-langchain4j-rag.yaml", + "sample.md" + ] + }, + { + "name": "financial-doc-analyzer", + "title": "Financial Document Analyzer", + "description": "Automated financial document analysis with Docling, LangChain4j, and market data", + "level": "advanced", + "tags": [ + "ai", + "langchain4j", + "docling", + "finance" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "examples/banking-sector-brief.pdf", + "examples/magnificent-seven-update.pdf", + "examples/semiconductor-sector-analysis.pdf", + "examples/tesla-q3-2024.pdf", + "financial-doc-analyzer.yaml" + ] + }, + { + "name": "ftp", + "title": "ActiveMQ to FTP", + "description": "Integrate ActiveMQ messaging with an FTP server", + "level": "intermediate", + "tags": [ + "messaging", + "ftp", + "activemq" + ], + "bundled": false, + "requiresDocker": true, + "files": [ + "README.adoc", + "application.properties", + "compose.yaml", + "ftp.camel.yaml", + "jbang.properties" + ] + }, + { + "name": "groovy", + "title": "Groovy", + "description": "Use Groovy with extra dependencies and content-based routing", + "level": "beginner", + "tags": [ + "language", + "groovy", + "eip" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "groovy.camel.yaml" + ] + }, + { + "name": "keycloak-introspection-rest", + "title": "Keycloak Token Introspection REST API", + "description": "Secure REST APIs with Keycloak OAuth 2.0 token introspection", + "level": "advanced", + "tags": [ + "security", + "keycloak", + "rest", + "oauth" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "rest-api.camel.yaml" + ] + }, + { + "name": "keycloak-security-rest", + "title": "Keycloak Security REST API", + "description": "Secure REST APIs with Keycloak authentication and authorization", + "level": "advanced", + "tags": [ + "security", + "keycloak", + "rest", + "oauth" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "rest-api.camel.yaml" + ] + }, + { + "name": "mqtt", + "title": "MQTT", + "description": "Receive MQTT events from an external MQTT broker", + "level": "intermediate", + "tags": [ + "messaging", + "mqtt", + "iot" + ], + "bundled": false, + "requiresDocker": true, + "files": [ + "README.adoc", + "application.properties", + "compose.yaml", + "infra/mosquitto.conf", + "mqtt.camel.yaml", + "start.sh" + ] + }, + { + "name": "openai/pii-redaction", + "title": "OpenAI PII Redaction", + "description": "Redact personal identifiable information from text using OpenAI-compatible LLMs", + "level": "advanced", + "tags": [ + "ai", + "openai", + "privacy", + "security" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "pii-redaction.camel.yaml", + "pii.schema.json" + ] + }, + { + "name": "openapi/client", + "title": "OpenAPI Client", + "description": "REST client generated from an OpenAPI specification", + "level": "intermediate", + "tags": [ + "rest", + "openapi", + "client" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "examples/1001.json", + "petstore-api.json", + "petstore-client.camel.yaml" + ] + }, + { + "name": "openapi/server", + "title": "OpenAPI Server", + "description": "REST service implemented from an OpenAPI specification", + "level": "intermediate", + "tags": [ + "rest", + "openapi", + "server" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "application.properties", + "examples/pet/1000.json", + "petstore-api.json", + "petstore.camel.yaml" + ] + }, + { + "name": "rest-api", + "title": "REST API", + "description": "REST API with hello endpoints", + "level": "beginner", + "tags": [ + "beginner", + "rest", + "http" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "rest-api.camel.yaml" + ] + }, + { + "name": "routes", + "title": "Routes", + "description": "Define routes in YAML with Java beans", + "level": "beginner", + "tags": [ + "beginner", + "yaml", + "bean" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "Greeter.java", + "README.adoc", + "beans.yaml", + "routes.camel.yaml" + ] + }, + { + "name": "smart-log-analyzer", + "title": "Smart Log Analyzer", + "description": "Intelligent observability correlating OpenTelemetry logs and traces with LLM analysis", + "level": "advanced", + "tags": [ + "ai", + "observability", + "opentelemetry", + "logging" + ], + "bundled": false, + "requiresDocker": false, + "files": [ + "README.adoc", + "analyzer/application-dev.properties", + "analyzer/error-analyzer.camel.yaml", + "containers/caches/infinispan-events-config.json", + "containers/caches/infinispan-events-to-process-config.json", + "containers/docker-compose.yaml", + "containers/otel-collector-config.yaml", + "correlator/application-dev.properties", + "correlator/correlated-log-schema.json", + "correlator/correlated-trace-schema.json", + "correlator/infinispan.camel.yaml", + "correlator/kafka-ca-cert.pem", + "correlator/kaoto-datamapper-4a94acc3.xsl", + "correlator/kaoto-datamapper-8f5bb2dd.xsl", + "correlator/logs-mapper.camel.yaml", + "correlator/otel-log-record-schema.json", + "correlator/otel-logs-schema.json", + "correlator/otel-span-schema.json", + "correlator/otel-traces-schema.json", + "correlator/traces-mapper.camel.yaml", + "first-iteration/analyzer.camel.yaml", + "first-iteration/application.properties", + "first-iteration/load-generator.camel.yaml", + "log-generator/agent.properties", + "log-generator/application-dev.properties", + "log-generator/log-generator.camel.yaml", + "log-generator/opentelemetry-javaagent.jar", + "ui-console/application-dev.properties", + "ui-console/index.html", + "ui-console/jms-file-storage.camel.yaml", + "ui-console/rest-api.camel.yaml" + ] + }, + { + "name": "sql", + "title": "SQL Database", + "description": "Use a SQL database with Camel and Postgres", + "level": "intermediate", + "tags": [ + "database", + "sql", + "postgres" + ], + "bundled": false, + "requiresDocker": true, + "files": [ + "README.adoc", + "application.properties", + "compose.yaml", + "sql.camel.yaml" + ] + }, + { + "name": "timer-log", + "title": "Timer Log", + "description": "Simple timer that logs a hello message every second", + "level": "beginner", + "tags": [ + "beginner", + "timer", + "log" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "timer-log.camel.yaml" + ] + }, + { + "name": "xslt", + "title": "XSLT Transformation", + "description": "Basic XML transformation using XSLT style sheets", + "level": "beginner", + "tags": [ + "transformation", + "xml", + "xslt" + ], + "bundled": true, + "requiresDocker": false, + "files": [ + "README.adoc", + "consumer.camel.yaml", + "input/account.xml", + "stylesheet.xsl" + ] + } +] diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc new file mode 100644 index 000000000000..cca5a275abf1 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc @@ -0,0 +1,61 @@ +== Circuit Breaker + +This example shows how Camel JBang can use circuit breaker EIP. + +=== Install JBang + +First install JBang according to https://www.jbang.dev + +When JBang is installed then you should be able to run from a shell: + +[source,sh] +---- +$ jbang --version +---- + +This will output the version of JBang. + +To run this example you can either install Camel on JBang via: + +[source,sh] +---- +$ jbang app install camel@apache/camel +---- + +Which allows to run Camel JBang with `camel` as shown below. + +=== How to run + +You can run this example using: + +[source,sh] +---- +$ camel run * +---- + +While the Camel integration is running, then from another terminal type: + +[source,sh] +---- +$ camel get circuit-breaker +---- + +Which then output the state of the circuit breaker. You can run this command with `--watch` and see +how the state of the circuit breaker changes from closed to open due to many failures. + +[source,sh] +---- +$ camel get circuit-breaker --watch +---- + + + +=== Help and contributions + +If you hit any problem using Camel or have some feedback, then please +https://camel.apache.org/community/support/[let us know]. + +We also love contributors, so +https://camel.apache.org/community/contributing/[get involved] :-) + +The Camel riders! diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/route.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/route.camel.yaml new file mode 100644 index 000000000000..7566bd348447 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/route.camel.yaml @@ -0,0 +1,43 @@ +# +# 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. +# + +- route: + from: + uri: timer:start + steps: + - setBody: + expression: + constant: + expression: Hello Camel + - circuitBreaker: + resilience4jConfiguration: + minimumNumberOfCalls: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 20 + steps: + - filter: + expression: + simple: + expression: ${random(10)} > 2 + steps: + - throwException: + message: Forced error + exceptionType: java.lang.IllegalArgumentException + - log: + message: "${body} (CircuitBreaker is open: ${exchangeProperty.CamelCircuitBreakerResponseShortCircuited})" + parameters: + period: 1000 diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml deleted file mode 100644 index c31bdb1cec3d..000000000000 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- route: - id: cron-log - from: - uri: timer:cron - parameters: - period: "5000" - steps: - - setBody: - simple: "Scheduled task running at ${date:now:HH:mm:ss}" - - log: "${body}" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc new file mode 100644 index 000000000000..7a5721140db9 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc @@ -0,0 +1,7 @@ +== Cron Log + +This example shows a scheduled task that logs the current time every 5 seconds. + +=== How to run + + camel run cron-log.camel.yaml diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml new file mode 100644 index 000000000000..fd729c9f5ea0 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml @@ -0,0 +1,27 @@ +# +# 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. +# + +- route: + id: cron-log + from: + uri: timer:cron + parameters: + period: "5000" + steps: + - setBody: + simple: "Scheduled task running at ${date:now:HH:mm:ss}" + - log: "${body}" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc new file mode 100644 index 000000000000..d6910f56215e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc @@ -0,0 +1,76 @@ +== Groovy + +This example shows how to use Groovy with extra dependencies in Camel JBang. + +The route uses `EmailValidator` from https://commons.apache.org/proper/commons-validator/[Apache Commons Validator] +to validate an email address and route the message accordingly using content-based routing. + +The extra dependency is declared in `application.properties` using the +`camel.jbang.dependencies` property: + +[source,properties] +---- +camel.jbang.dependencies=commons-validator:commons-validator:1.10.1 +---- + +=== Install JBang + +First install JBang according to https://www.jbang.dev + +When JBang is installed then you should be able to run from a shell: + +[source,sh] +---- +$ jbang --version +---- + +This will output the version of JBang. + +To run this example you can either install Camel on JBang via: + +[source,sh] +---- +$ jbang app install camel@apache/camel +---- + +Which allows to run Camel JBang with `camel` as shown below. + +=== How to run + +You can run this example using: + +[source,sh] +---- +$ camel run * +---- + +To see the invalid email branch, edit `groovy.camel.yaml` and change the `contactEmail` header in the `once` URI to an invalid value: + +[source,yaml] +---- +uri: once:validate?header.contactEmail=not-a-valid-email +---- + +You can also declare dependencies as a modeline comment at the top of the YAML route file: + +[source,yaml] +---- +#//DEPS commons-validator:commons-validator:1.10.1 +---- + +Or pass the dependency on the command line: + +[source,sh] +---- +$ camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1 +---- + +=== Help and contributions + +If you hit any problem using Camel or have some feedback, then please +https://camel.apache.org/community/support/[let us know]. + +We also love contributors, so +https://camel.apache.org/community/contributing/[get involved] :-) + +The Camel riders! diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/application.properties new file mode 100644 index 000000000000..0bea6e4cd400 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/application.properties @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +camel.jbang.dependencies=commons-validator:commons-validator:1.10.1 diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml new file mode 100644 index 000000000000..2b89a31150bb --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml @@ -0,0 +1,49 @@ +# +# 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. +# + +- route: + id: route-1681 + from: + id: from-1133 + uri: once + parameters: + name: validate + steps: + - setHeader: + id: setHeader-4262 + constant: + expression: [email protected] + name: contactEmail + - choice: + id: choice-1178 + otherwise: + id: otherwise-1234 + steps: + - log: + id: log-3646 + message: "Invalid email: ${header.contactEmail}" + when: + - id: when-1441 + steps: + - log: + id: log-2349 + message: "Valid contact: ${header.contactEmail}" + groovy: + expression: >- + import org.apache.commons.validator.routines.EmailValidator + + EmailValidator.getInstance().isValid(request.headers['contactEmail']) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml deleted file mode 100644 index 2e1035cf1f0f..000000000000 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml +++ /dev/null @@ -1,21 +0,0 @@ -- rest: - path: /api - get: - - path: /hello - to: direct:hello - - path: /hello/{name} - to: direct:hello-name -- route: - id: hello - from: - uri: direct:hello - steps: - - setBody: - constant: "Hello from Camel REST API!" -- route: - id: hello-name - from: - uri: direct:hello-name - steps: - - setBody: - simple: "Hello ${header.name} from Camel REST API!" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc new file mode 100644 index 000000000000..c68c2872843b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc @@ -0,0 +1,12 @@ +== REST API + +This example shows a REST API with hello endpoints. + +=== How to run + + camel run rest-api.camel.yaml + +=== Try it + + curl http://localhost:8080/api/hello + curl http://localhost:8080/api/hello/World diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml new file mode 100644 index 000000000000..dda5c6cba0fc --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml @@ -0,0 +1,38 @@ +# +# 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. +# + +- rest: + path: /api + get: + - path: /hello + to: direct:hello + - path: /hello/{name} + to: direct:hello-name +- route: + id: hello + from: + uri: direct:hello + steps: + - setBody: + constant: "Hello from Camel REST API!" +- route: + id: hello-name + from: + uri: direct:hello-name + steps: + - setBody: + simple: "Hello ${header.name} from Camel REST API!" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/Greeter.java b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/Greeter.java new file mode 100644 index 000000000000..1fe603c4eeef --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/Greeter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package camel.example; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; + +public class Greeter implements Processor { + + private String message; + + public void setMessage(String message) { + this.message = message; + } + + @Override + public void process(Exchange exchange) throws Exception { + String body = exchange.getIn().getBody(String.class); + exchange.getIn().setBody(message + " " + body); + } + +} \ No newline at end of file diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc new file mode 100644 index 000000000000..5cf864ca5b61 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc @@ -0,0 +1,78 @@ +== Routes + +This example shows how routes are defined in Yaml. + +=== Install JBang + +First install JBang according to https://www.jbang.dev + +When JBang is installed then you should be able to run from a shell: + +[source,sh] +---- +$ jbang --version +---- + +This will output the version of JBang. + +To run this example you can either install Camel on JBang via: + +[source,sh] +---- +$ jbang app install camel@apache/camel +---- + +Which allows to run Camel JBang with `camel` as shown below. + +=== How to run + +You can run this example using: + +[source,sh] +---- +$ camel run * +---- + +Camel will start a route that periodically provides a greeting message. + +=== Live reload + +You can run the example in dev mode which allows you to edit the example, +and hot-reload when the file is saved. + +[source,sh] +---- +$ camel run * --dev +---- + +=== Run directly from GitHub + +The example can also be run directly by referring to the GitHub URL as shown: + +[source,sh] +---- +$ camel run https://github.com/apache/camel-jbang-examples/tree/main/routes +---- + +=== Developer Web Console + +You can enable the developer console via `--console` flag as show: + +[source,sh] +---- +$ camel run * --console +---- + +Then you can browse: http://localhost:8080/q/dev to introspect the running Camel Application. +Under "beans" Camel should display bean `greeter`. + + +=== Help and contributions + +If you hit any problem using Camel or have some feedback, then please +https://camel.apache.org/community/support/[let us know]. + +We also love contributors, so +https://camel.apache.org/community/contributing/[get involved] :-) + +The Camel riders! diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/beans.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/beans.yaml new file mode 100644 index 000000000000..70bc6a6d5d35 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/beans.yaml @@ -0,0 +1,22 @@ +# +# 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. +# + +- beans: + - name: "greeter" + type: "camel.example.Greeter" + properties: + message: 'Hello!' diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml new file mode 100644 index 000000000000..a1bfb7dcb291 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml @@ -0,0 +1,29 @@ +# +# 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. +# + +- route: + id: greeting-route + from: + uri: timer:start + parameters: + period: 1000 + steps: + - setBody: + simple: I'm ${routeId} + - bean: + ref: greeter + - log: ${body} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml deleted file mode 100644 index cdff71b4d71f..000000000000 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- route: - id: timer-log - from: - uri: timer:tick - parameters: - period: "1000" - steps: - - setBody: - simple: "Hello Camel! (message #${exchangeProperty.CamelTimerCounter})" - - log: "${body}" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc new file mode 100644 index 000000000000..3abb3a0f9b31 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc @@ -0,0 +1,7 @@ +== Timer Log + +This example shows a simple timer that logs a hello message every second. + +=== How to run + + camel run timer-log.camel.yaml diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml new file mode 100644 index 000000000000..48124209c60d --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml @@ -0,0 +1,27 @@ +# +# 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. +# + +- route: + id: timer-log + from: + uri: timer:tick + parameters: + period: "1000" + steps: + - setBody: + simple: "Hello Camel! (message #${exchangeProperty.CamelTimerCounter})" + - log: "${body}" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc new file mode 100644 index 000000000000..0d3cc77abf8e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc @@ -0,0 +1,66 @@ +== XSLT Transformation + +This example shows a basic XML transformation using XSLT style sheet. + +=== Install JBang + +First install JBang according to https://www.jbang.dev + +When JBang is installed then you should be able to run from a shell: + +[source,sh] +---- +$ jbang --version +---- + +This will output the version of JBang. + +To run this example you can either install Camel on JBang via: + +[source,sh] +---- +$ jbang app install camel@apache/camel +---- + +Which allows to run Camel JBang with `camel` as shown below. + +=== How to run + +Then you can run this example using: + +[source,sh] +---- +$ camel run * +---- + +This reads the XML input file from _./input/account.xml_ and applies XSL transformation. + +=== Live updates of message transformation + +You can do live changes to the stylesheet and see the output in real-time with Camel JBang by running: + +[source,bash] +---- +$ camel transform message --body=file:input/account.xml --component=xslt --template=file:stylesheet.xsl --pretty --watch +---- + +You can then edit the `stylesheet.xsl` file, and save the file, and watch the terminal for updated result. + +=== Run directly from GitHub + +The example can also be run directly by referring to the GitHub URL as shown: + +[source,sh] +---- +$ camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt +---- + +=== Help and contributions + +If you hit any problem using Camel or have some feedback, then please +https://camel.apache.org/community/support/[let us know]. + +We also love contributors, so +https://camel.apache.org/community/contributing/[get involved] :-) + +The Camel riders! diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml new file mode 100644 index 000000000000..18c6663e29aa --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml @@ -0,0 +1,30 @@ +# +# 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. +# + +- route: + from: + uri: file://input + parameters: + fileName: account.xml + noop: true + steps: + - to: + uri: xslt + parameters: + resourceUri: stylesheet.xsl + - log: + message: Transformed data:\n ${prettyBody} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/input/account.xml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/input/account.xml new file mode 100644 index 000000000000..50b8c1fe887b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/input/account.xml @@ -0,0 +1,27 @@ +<!-- + + 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. + +--> +<hash> + <id type="integer">1369</id> + <uid>8c946e1a-fdc5-40d3-9098-44271bdfad65</uid> + <account-number>8673088731</account-number> + <iban>GB38EFUA27474531363797</iban> + <bank-name>ABN AMRO MEZZANINE (UK) LIMITED</bank-name> + <routing-number>053228004</routing-number> + <swift-bic>AACCGB21</swift-bic> +</hash> \ No newline at end of file diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/stylesheet.xsl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/stylesheet.xsl new file mode 100644 index 000000000000..fdbb608b22b7 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/stylesheet.xsl @@ -0,0 +1,29 @@ +<?xml version = "1.0"?> +<!-- + + 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. + +--> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:template match="/"> + <bank> + <name><xsl:value-of select="/hash/bank-name/text()"/></name> + <bic><xsl:value-of select="/hash/swift-bic/text()"/></bic> + </bank> + </xsl:template> + +</xsl:stylesheet> diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java index 60aff3832db7..638eeaebfd21 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java @@ -58,15 +58,27 @@ class RunTest extends CamelCommandBaseTestSupport { @Test public void shouldListExamples() throws Exception { Run command = new Run(new CamelJBangMain().withPrinter(printer)); - command.exampleList = true; + command.exampleFilter = ""; int exit = command.doCall(); Assertions.assertEquals(0, exit); String output = printer.getOutput(); - Assertions.assertTrue(output.contains("Available built-in examples:")); - Assertions.assertTrue(output.contains("timer-log")); - Assertions.assertTrue(output.contains("rest-api")); - Assertions.assertTrue(output.contains("cron-log")); + Assertions.assertTrue(output.contains("Available examples:")); + Assertions.assertTrue(output.contains("circuit-breaker")); + Assertions.assertTrue(output.contains("groovy")); + Assertions.assertTrue(output.contains("routes")); + } + + @Test + public void shouldListExamplesWithFilter() throws Exception { + Run command = new Run(new CamelJBangMain().withPrinter(printer)); + command.exampleFilter = "security"; + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + String output = printer.getOutput(); + Assertions.assertTrue(output.contains("keycloak")); + Assertions.assertFalse(output.contains("circuit-breaker")); } @Test @@ -77,7 +89,7 @@ class RunTest extends CamelCommandBaseTestSupport { Assertions.assertEquals(0, exit); String output = printer.getOutput(); - Assertions.assertTrue(output.contains("Available built-in examples:")); + Assertions.assertTrue(output.contains("Available examples:")); } @Test @@ -89,12 +101,23 @@ class RunTest extends CamelCommandBaseTestSupport { Assertions.assertEquals(1, exit); } + @Test + public void shouldSuggestSimilarExample() throws Exception { + Run command = new Run(new CamelJBangMain().withPrinter(printer)); + command.example = "circuit-brake"; + int exit = command.doCall(); + + Assertions.assertEquals(1, exit); + String output = printer.getOutput(); + Assertions.assertTrue(output.contains("Did you mean")); + } + @Test public void shouldParseExampleOption() throws Exception { Run command = new Run(new CamelJBangMain()); - CommandLine.populateCommand(command, "--example=timer-log"); + CommandLine.populateCommand(command, "--example=circuit-breaker"); - Assertions.assertEquals("timer-log", command.example); + Assertions.assertEquals("circuit-breaker", command.example); } @Test @@ -102,6 +125,6 @@ class RunTest extends CamelCommandBaseTestSupport { Run command = new Run(new CamelJBangMain()); CommandLine.populateCommand(command, "--example-list"); - Assertions.assertTrue(command.exampleList); + Assertions.assertNotNull(command.exampleFilter); } } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java new file mode 100644 index 000000000000..1e6473bcfd0f --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java @@ -0,0 +1,155 @@ +/* + * 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.common; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ExampleHelperTest { + + @Test + void shouldLoadCatalog() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + assertFalse(catalog.isEmpty()); + } + + @Test + void shouldFindExample() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "circuit-breaker"); + assertNotNull(entry); + assertEquals("Circuit Breaker", entry.getString("title")); + } + + @Test + void shouldReturnNullForUnknownExample() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "does-not-exist"); + assertNull(entry); + } + + @Test + void shouldFilterByTag() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, "security"); + assertFalse(filtered.isEmpty()); + for (JsonObject entry : filtered) { + String name = entry.getString("name"); + assertTrue(name.contains("keycloak") || name.contains("pqc") || name.contains("ocsf") + || name.contains("pii"), + "Expected security-related example but got: " + name); + } + } + + @Test + void shouldFilterByName() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, "mqtt"); + assertEquals(1, filtered.size()); + assertEquals("mqtt", filtered.get(0).getString("name")); + } + + @Test + void shouldReturnAllWhenFilterEmpty() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, ""); + assertEquals(catalog.size(), filtered.size()); + } + + @Test + void shouldDetectBundled() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject circuitBreaker = ExampleHelper.findExample(catalog, "circuit-breaker"); + assertTrue(ExampleHelper.isBundled(circuitBreaker)); + + JsonObject mqtt = ExampleHelper.findExample(catalog, "mqtt"); + assertFalse(ExampleHelper.isBundled(mqtt)); + } + + @Test + void shouldDetectDocker() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject mqtt = ExampleHelper.findExample(catalog, "mqtt"); + assertTrue(ExampleHelper.requiresDocker(mqtt)); + + JsonObject circuitBreaker = ExampleHelper.findExample(catalog, "circuit-breaker"); + assertFalse(ExampleHelper.requiresDocker(circuitBreaker)); + } + + @Test + void shouldGetFiles() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject routes = ExampleHelper.findExample(catalog, "routes"); + List<String> files = ExampleHelper.getFiles(routes); + assertTrue(files.contains("routes.camel.yaml")); + assertTrue(files.contains("Greeter.java")); + assertTrue(files.contains("beans.yaml")); + } + + @Test + void shouldExtractBundledExample() throws Exception { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "circuit-breaker"); + Path tempDir = ExampleHelper.extractBundledExample(entry); + + assertTrue(Files.exists(tempDir.resolve("route.camel.yaml"))); + String content = Files.readString(tempDir.resolve("route.camel.yaml")); + assertFalse(content.isEmpty()); + } + + @Test + void shouldExtractBundledExampleWithSubdirectory() throws Exception { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "xslt"); + Path tempDir = ExampleHelper.extractBundledExample(entry); + + assertTrue(Files.exists(tempDir.resolve("consumer.camel.yaml"))); + assertTrue(Files.exists(tempDir.resolve("stylesheet.xsl"))); + assertTrue(Files.exists(tempDir.resolve("input/account.xml"))); + } + + @Test + void shouldGetGithubUrl() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "mqtt"); + String url = ExampleHelper.getGithubUrl(entry); + assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/mqtt", url); + } + + @Test + void shouldGetGithubUrlForNestedExample() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + JsonObject entry = ExampleHelper.findExample(catalog, "aws/aws-sqs"); + String url = ExampleHelper.getGithubUrl(entry); + assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/aws/aws-sqs", url); + } + + @Test + void shouldGetExampleNames() { + List<JsonObject> catalog = ExampleHelper.loadCatalog(); + List<String> names = ExampleHelper.getExampleNames(catalog); + assertTrue(names.contains("circuit-breaker")); + assertTrue(names.contains("mqtt")); + assertTrue(names.contains("aws/aws-sqs")); + } +}
