Croway commented on code in PR #21534: URL: https://github.com/apache/camel/pull/21534#discussion_r2826742966
########## dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Diagram.java: ########## @@ -0,0 +1,945 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.ThreadLocalRandom; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Page.NavigateOptions; +import com.microsoft.playwright.Page.ScreenshotOptions; +import com.microsoft.playwright.Page.WaitForFunctionOptions; +import com.microsoft.playwright.Page.WaitForSelectorOptions; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.options.ViewportSize; +import com.microsoft.playwright.options.WaitUntilState; +import org.apache.camel.dsl.jbang.core.commands.process.Hawtio; +import org.apache.camel.dsl.jbang.core.commands.process.Jolokia; +import org.apache.camel.dsl.jbang.core.commands.process.StopProcess; +import org.apache.camel.util.FileUtil; +import org.apache.camel.util.StopWatch; +import picocli.CommandLine; + [email protected](name = "diagram", description = "Visualize Camel routes using Hawtio", sortOptions = false, + showDefaultValues = true) +public class Diagram extends CamelCommand { + + @CommandLine.Parameters(description = "The Camel file(s) to run. If no files specified then use --name to attach to a running integration.", + arity = "0..9", paramLabel = "<files>", parameterConsumer = FilesConsumer.class) + Path[] filePaths; // Defined only for file path completion; the field never used + List<String> files = new ArrayList<>(); + + @CommandLine.Option(names = { "--name" }, + description = "Name or pid of running Camel integration") + String name; + + @CommandLine.Option(names = { "--renderer" }, + description = "Renderer to use (hawtio)", + defaultValue = "hawtio") + String renderer = "hawtio"; + + @CommandLine.Option(names = { "--port" }, + description = "Port number to use for Hawtio web console (port 8888 by default)", defaultValue = "8888") + int port = 8888; + + @CommandLine.Option(names = { "--openUrl" }, + description = "To automatic open Hawtio web console in the web browser", defaultValue = "true") + boolean openUrl = true; + + @CommandLine.Option(names = { "--background-wait" }, defaultValue = "true", + description = "To wait for run in background to startup successfully, before returning") + boolean backgroundWait = true; + + @CommandLine.Option(names = { "--keep-running" }, defaultValue = "false", + description = "Keep the background Camel integration running after exiting Hawtio") + boolean keepRunning; + + @CommandLine.Option(names = { "--output" }, + description = "Write a PNG snapshot of the route diagram to the given file") + Path output; + + @CommandLine.Option(names = { "--browser" }, + description = "Playwright browser to use (chromium only)", + defaultValue = "chromium") + String browser = "chromium"; + + @CommandLine.Option(names = { "--playwright-browser-path" }, + description = "Path to the Playwright browser executable") + String playwrightBrowserPath; + + @CommandLine.Option(names = { "--playwright-timeout" }, defaultValue = "120000", + description = "Timeout in millis for Playwright navigation and rendering") + long playwrightTimeout = 120000; + + @CommandLine.Option(names = { "--route-id" }, + description = "Route id to render (defaults to the first route)") + String routeId; + + @CommandLine.Option(names = { "--jolokia-port" }, defaultValue = "8778", + description = "Jolokia port to attach when exporting PNG") + int jolokiaPort = 8778; + + private boolean jolokiaAttached; + + public Diagram(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + String selectedRenderer = renderer == null ? "hawtio" : renderer.toLowerCase(Locale.ROOT); + if (!"hawtio".equals(selectedRenderer)) { + printer().printErr("Unsupported renderer: " + renderer); + return 1; + } + + boolean hasFiles = files != null && !files.isEmpty(); + boolean exportPng = output != null; + if (exportPng && openUrl) { + openUrl = false; + } + + String runName = name; + if (hasFiles && (runName == null || runName.isBlank())) { + runName = FileUtil.onlyName(FileUtil.stripPath(files.get(0))); + } + String target = runName; + if (!hasFiles && (target == null || target.isBlank())) { + new CommandLine(this).execute("--help"); + return 0; + } + + long pid = 0; + boolean started = false; + int exit = 0; + try { + if (hasFiles) { + Run run = new Run(getMain()); + run.backgroundWait = backgroundWait; + if (runName != null && !runName.isBlank()) { + run.name = runName; + } + List<String> args = new ArrayList<>(); + args.add("run"); + if (runName != null && !runName.isBlank()) { + args.add("--name=" + runName); + } + args.addAll(files); + RunHelper.addCamelCLICommand(args); + ProcessBuilder pb = new ProcessBuilder(); + pb.command(args); + int rc = run.runBackgroundProcess(pb, "Camel Main"); + if (rc != 0) { + return rc; + } + pid = run.spawnPid; + if (pid <= 0) { + printer().printErr("Unable to determine the running Camel PID"); + return 1; + } + target = Long.toString(pid); + started = true; + } + + if (exportPng) { + String hawtioUrl = "http://localhost:" + port + "/hawtio"; + String jolokiaUrl = "http://127.0.0.1:" + jolokiaPort + "/jolokia"; + int attachCode = attachJolokia(target, jolokiaUrl); + if (attachCode != 0) { + return attachCode; + } + HawtioProcess hawtioProcess = startHawtioProcess(port); + try { + waitForHawtio(hawtioUrl, hawtioProcess); + exit = exportDiagramPng(hawtioUrl, jolokiaUrl); + } finally { + stopProcess(hawtioProcess.process); + if (keepRunning && jolokiaAttached) { + detachJolokia(target); + } + } + return exit; + } else { + Hawtio hawtio = new Hawtio(getMain()); + List<String> hawtioArgs = new ArrayList<>(); + if (target != null && !target.isBlank()) { + hawtioArgs.add(target); + } + hawtioArgs.add("--port=" + port); + hawtioArgs.add("--openUrl=" + openUrl); + CommandLine.populateCommand(hawtio, hawtioArgs.toArray(new String[0])); + exit = hawtio.doCall(); + return exit; + } + } finally { + if (started && !keepRunning) { + StopProcess stop = new StopProcess(getMain()); + if (target != null && !target.isBlank()) { + CommandLine.populateCommand(stop, target); + } + stop.doCall(); + } + } + } + + private int attachJolokia(String target, String jolokiaUrl) throws Exception { + if (target == null || target.isBlank()) { + printer().printErr("Name or PID required to attach Jolokia for PNG export"); + return 1; + } + jolokiaAttached = false; + if (isJolokiaAvailable(jolokiaUrl)) { + return 0; + } + Jolokia jolokia = new Jolokia(getMain()); + List<String> args = new ArrayList<>(); + args.add(target); + args.add("--port=" + jolokiaPort); + CommandLine.populateCommand(jolokia, args.toArray(new String[0])); + int code = jolokia.doCall(); + if (code != 0) { + return waitForJolokia(jolokiaUrl, 5000) ? 0 : code; + } + if (waitForJolokia(jolokiaUrl, 5000)) { + jolokiaAttached = true; + return 0; + } + printer().printErr("Unable to attach Jolokia at " + jolokiaUrl); + return 1; + } + + private void detachJolokia(String target) { + try { + if (target == null || target.isBlank()) { + return; + } + Jolokia jolokia = new Jolokia(getMain()); + CommandLine.populateCommand(jolokia, "--stop", target); + jolokia.doCall(); + } catch (Exception e) { + printer().printErr("Failed to stop Jolokia: " + e.getMessage()); + } + } + + private HawtioProcess startHawtioProcess(int port) throws Exception { + List<String> args = new ArrayList<>(); + args.add("hawtio"); + args.add("--port=" + port); + args.add("--openUrl=false"); + RunHelper.addCamelCLICommand(args); + ProcessBuilder pb = new ProcessBuilder(); + pb.command(args); + pb.redirectErrorStream(true); + Process process = pb.start(); + List<String> output = Collections.synchronizedList(new ArrayList<>()); + startOutputPump(process.getInputStream(), output); + return new HawtioProcess(process, output); + } + + private void stopProcess(Process process) { + if (process == null) { + return; + } + try { + process.destroy(); + process.waitFor(); + } catch (Exception e) { + // ignore + } + } + + private void waitForHawtio(String hawtioUrl, HawtioProcess hawtioProcess) throws Exception { + StopWatch watch = new StopWatch(); + while (watch.taken() < playwrightTimeout) { + try { + URL url = URI.create(hawtioUrl).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(1000); + conn.setReadTimeout(1000); + conn.setRequestMethod("GET"); + int code = conn.getResponseCode(); + if (code >= 200 && code < 500) { + return; + } + } catch (Exception e) { + // ignore until timeout + } + if (hawtioProcess != null && !hawtioProcess.process.isAlive()) { + throw new IllegalStateException("Hawtio terminated before startup." + formatHawtioOutput(hawtioProcess.output)); + } + Thread.sleep(500); Review Comment: Use something else instead of Thread.sleep, playwright should have APIs -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
