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

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

commit 31ce7ba3016b458e375726f32d1612c72b786c42
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri May 29 09:31:56 2026 +0200

    CAMEL-23615: camel-jbang - TUI auto-start infra services when running 
examples
    
    Co-Authored-By: Claude <[email protected]>
---
 .../camel/dsl/jbang/core/common/ExampleHelper.java |   9 ++
 .../examples/camel-jbang-example-catalog.json      |  29 ++++-
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 138 ++++++++++++++++-----
 3 files changed, 144 insertions(+), 32 deletions(-)

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
index faf401146db3..5e934d366d66 100644
--- 
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
@@ -129,6 +129,15 @@ public final class ExampleHelper {
         return citrus != null && citrus;
     }
 
+    @SuppressWarnings("unchecked")
+    public static List<String> getInfraServices(JsonObject entry) {
+        Collection<String> services = (Collection<String>) 
entry.get("infraServices");
+        if (services == null) {
+            return List.of();
+        }
+        return new ArrayList<>(services);
+    }
+
     @SuppressWarnings("unchecked")
     public static List<String> getFiles(JsonObject entry) {
         Collection<String> files = (Collection<String>) entry.get("files");
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
index d88e50b1dc0f..10c4a6e65692 100644
--- 
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
@@ -17,6 +17,9 @@
             "application.properties",
             "consumer.camel.yaml",
             "producer.camel.yaml"
+        ],
+        "infraServices": [
+            "artemis"
         ]
     },
     {
@@ -120,6 +123,10 @@
             "compose.yaml",
             "docling-langchain4j-rag.yaml",
             "sample.md"
+        ],
+        "infraServices": [
+            "docling",
+            "ollama"
         ]
     },
     {
@@ -144,6 +151,10 @@
             "examples/semiconductor-sector-analysis.pdf",
             "examples/tesla-q3-2024.pdf",
             "financial-doc-analyzer.yaml"
+        ],
+        "infraServices": [
+            "docling",
+            "ollama"
         ]
     },
     {
@@ -165,6 +176,10 @@
             "compose.yaml",
             "ftp.camel.yaml",
             "jbang.properties"
+        ],
+        "infraServices": [
+            "artemis",
+            "ftp"
         ]
     },
     {
@@ -204,6 +219,9 @@
             "README.md",
             "application.properties",
             "rest-api.camel.yaml"
+        ],
+        "infraServices": [
+            "keycloak"
         ]
     },
     {
@@ -224,6 +242,9 @@
             "README.md",
             "application.properties",
             "rest-api.camel.yaml"
+        ],
+        "infraServices": [
+            "keycloak"
         ]
     },
     {
@@ -241,7 +262,7 @@
         "hasCitrusTests": false,
         "files": [
             "README.md",
-            "message-size.camel.yaml"
+            "orders.camel.yaml"
         ]
     },
     {
@@ -264,6 +285,9 @@
             "infra/mosquitto.conf",
             "mqtt.camel.yaml",
             "start.sh"
+        ],
+        "infraServices": [
+            "mosquitto"
         ]
     },
     {
@@ -435,6 +459,9 @@
             "application.properties",
             "compose.yaml",
             "sql.camel.yaml"
+        ],
+        "infraServices": [
+            "postgres"
         ]
     },
     {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 831f7937d8ad..f737a9af1e73 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -143,6 +143,7 @@ class ActionsPopup {
     private ScheduledExecutorService scheduler;
 
     private final List<PendingLaunch> pendingLaunches = new ArrayList<>();
+    private DeferredExampleLaunch deferredLaunch;
     private String launchNotification;
     private boolean launchNotificationError;
     private long launchNotificationExpiry;
@@ -651,6 +652,7 @@ class ActionsPopup {
 
     void tick(long now) {
         monitorPendingLaunches(now);
+        checkDeferredLaunch(now);
         if (launchNotification != null && now > launchNotificationExpiry) {
             launchNotification = null;
         }
@@ -764,8 +766,10 @@ class ActionsPopup {
             boolean docker = ExampleHelper.requiresDocker(ex);
             boolean bundled = ExampleHelper.isBundled(ex);
             boolean citrus = ExampleHelper.hasCitrusTests(ex);
+            boolean infra = !ExampleHelper.getInfraServices(ex).isEmpty();
 
-            String icons = (bundled ? "📦" : "🌐") + (docker ? "🐳" : "  ") + 
(citrus ? "🍋" : "  ");
+            String icons = (bundled ? "📦" : "🌐") + (docker ? "🐳" : "  ")
+                           + (infra ? "🔧" : "  ") + (citrus ? "🍋" : "  ");
             int nameCol = Math.min(30, width / 3);
             String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(name, nameCol));
             String prefix = " " + icons + " " + padded + " ";
@@ -786,7 +790,7 @@ class ActionsPopup {
             }
         }
         items.add(ListItem.from(""));
-        items.add(ListItem.from(" 📦 = bundled (offline)  🌐 = online (GitHub)  
🐳 = Docker  🍋 = Citrus tests")
+        items.add(ListItem.from(" 📦 = bundled  🌐 = online  🐳 = Docker  🔧 = 
infra services  🍋 = Citrus tests")
                 .style(Style.EMPTY.dim()));
         return items;
     }
@@ -1160,28 +1164,18 @@ class ActionsPopup {
         }
         List<String> extraArgs = runOptionsForm.buildArgs();
         runOptionsForm.close();
-        try {
-            List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
-            cmd.add("run");
-            cmd.add("--example=" + exampleName);
-            cmd.add("--logging-color=true");
-            cmd.addAll(extraArgs);
-            Path outputFile = Files.createTempFile("camel-example-", ".log");
-            outputFile.toFile().deleteOnExit();
-            ProcessBuilder pb = new ProcessBuilder(cmd);
-            pb.redirectErrorStream(true);
-            pb.redirectOutput(outputFile.toFile());
-            Process process = pb.start();
-            pendingLaunches.add(new PendingLaunch(displayName, process, 
outputFile, System.currentTimeMillis()));
-            pendingAutoSelect = displayName;
-            launchNotification = "Starting: " + displayName;
-            launchNotificationError = false;
-            launchNotificationExpiry = System.currentTimeMillis() + 5000;
-        } catch (Exception e) {
-            launchNotification = "Failed to start: " + exampleName + " - " + 
e.getMessage();
-            launchNotificationError = true;
-            launchNotificationExpiry = System.currentTimeMillis() + 10000;
+
+        List<String> missing = findMissingInfraServices(selectedExample);
+        if (!missing.isEmpty()) {
+            if (!isContainerRuntimeAvailable()) {
+                setNotification("Docker/Podman required for infra services. 
Run Doctor for details", true);
+                return;
+            }
+            startMissingInfraAndDeferExample(missing, exampleName, 
displayName, extraArgs);
+            return;
         }
+
+        doLaunchExample(exampleName, displayName, extraArgs);
     }
 
     // ---- Example Browser Navigation ----
@@ -1641,26 +1635,103 @@ class ActionsPopup {
         }
         String exampleName = example.getStringOrDefault("name", "");
         showExampleBrowser = false;
+
+        List<String> missing = findMissingInfraServices(example);
+        if (!missing.isEmpty()) {
+            if (!isContainerRuntimeAvailable()) {
+                setNotification("Docker/Podman required for infra services. 
Run Doctor for details", true);
+                return;
+            }
+            startMissingInfraAndDeferExample(missing, exampleName, 
exampleName, List.of());
+            return;
+        }
+
+        doLaunchExample(exampleName, exampleName, List.of());
+    }
+
+    private void doLaunchExample(String exampleName, String displayName, 
List<String> extraArgs) {
         try {
             List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
             cmd.add("run");
             cmd.add("--example=" + exampleName);
             cmd.add("--logging-color=true");
+            cmd.addAll(extraArgs);
             Path outputFile = Files.createTempFile("camel-example-", ".log");
             outputFile.toFile().deleteOnExit();
             ProcessBuilder pb = new ProcessBuilder(cmd);
             pb.redirectErrorStream(true);
             pb.redirectOutput(outputFile.toFile());
             Process process = pb.start();
-            pendingLaunches.add(new PendingLaunch(exampleName, process, 
outputFile, System.currentTimeMillis()));
-            pendingAutoSelect = exampleName;
-            launchNotification = "Starting: " + exampleName;
-            launchNotificationError = false;
-            launchNotificationExpiry = System.currentTimeMillis() + 5000;
+            pendingLaunches.add(new PendingLaunch(displayName, process, 
outputFile, System.currentTimeMillis()));
+            pendingAutoSelect = displayName;
+            setNotification("Starting: " + displayName, false);
         } catch (Exception e) {
-            launchNotification = "Failed to start: " + exampleName + " - " + 
e.getMessage();
-            launchNotificationError = true;
-            launchNotificationExpiry = System.currentTimeMillis() + 10000;
+            setNotification("Failed to start: " + exampleName + " - " + 
e.getMessage(), true);
+        }
+    }
+
+    private List<String> findMissingInfraServices(JsonObject example) {
+        List<String> required = ExampleHelper.getInfraServices(example);
+        if (required.isEmpty()) {
+            return List.of();
+        }
+        Set<String> runningAliases = infraServices.get().stream()
+                .filter(i -> i.alive)
+                .map(i -> i.alias)
+                .collect(Collectors.toSet());
+        List<String> missing = new ArrayList<>();
+        for (String alias : required) {
+            if (!runningAliases.contains(alias)) {
+                missing.add(alias);
+            }
+        }
+        return missing;
+    }
+
+    private void startMissingInfraAndDeferExample(
+            List<String> missingInfra, String exampleName, String displayName, 
List<String> extraArgs) {
+        for (String alias : missingInfra) {
+            try {
+                List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
+                cmd.add("infra");
+                cmd.add("run");
+                cmd.add(alias);
+                cmd.add("--background");
+                Path outputFile = Files.createTempFile("camel-infra-", ".log");
+                outputFile.toFile().deleteOnExit();
+                ProcessBuilder pb = new ProcessBuilder(cmd);
+                pb.redirectErrorStream(true);
+                pb.redirectOutput(outputFile.toFile());
+                Process process = pb.start();
+                pendingLaunches.add(new PendingLaunch(alias, process, 
outputFile, System.currentTimeMillis()));
+            } catch (Exception e) {
+                setNotification("Failed to start infra: " + alias + " - " + 
e.getMessage(), true);
+                return;
+            }
+        }
+        deferredLaunch = new DeferredExampleLaunch(
+                exampleName, displayName, extraArgs, missingInfra, 
System.currentTimeMillis());
+        infraCatalog = null;
+        String infraList = String.join(", ", missingInfra);
+        setNotification("Starting infra: " + infraList + " → then: " + 
displayName, false);
+    }
+
+    private void checkDeferredLaunch(long now) {
+        if (deferredLaunch == null) {
+            return;
+        }
+        Set<String> runningAliases = infraServices.get().stream()
+                .filter(i -> i.alive)
+                .map(i -> i.alias)
+                .collect(Collectors.toSet());
+        boolean allReady = 
runningAliases.containsAll(deferredLaunch.requiredInfra);
+        if (allReady) {
+            DeferredExampleLaunch dl = deferredLaunch;
+            deferredLaunch = null;
+            doLaunchExample(dl.exampleName, dl.displayName, dl.extraArgs);
+        } else if (now - deferredLaunch.startTime > 120_000) {
+            deferredLaunch = null;
+            setNotification("Timeout waiting for infra services to start", 
true);
         }
     }
 
@@ -1758,4 +1829,9 @@ class ActionsPopup {
 
     private record PendingLaunch(String name, Process process, Path 
outputFile, long startTime) {
     }
+
+    private record DeferredExampleLaunch(
+            String exampleName, String displayName, List<String> extraArgs,
+            List<String> requiredInfra, long startTime) {
+    }
 }

Reply via email to