davsclaus commented on code in PR #23333:
URL: https://github.com/apache/camel/pull/23333#discussion_r3275438769


##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java:
##########
@@ -0,0 +1,125 @@
+/*
+ * 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.util.Properties;
+import java.util.function.Function;
+
+import org.apache.camel.dsl.jbang.core.common.QuarkusHelper;
+import org.apache.camel.dsl.jbang.core.common.QuarkusHelper.QuarkusPlatformBom;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenDownloader;
+import org.apache.camel.tooling.maven.MavenGav;
+import picocli.CommandLine;
+
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_EXTENSION_REGISTRY_BASE_URI;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_GROUP_ID;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_VERSION;
+
+/**
+ * Options related to Quarkus Platform.
+ */
+public class QuarkusPlatformMixin extends QuarkusExtensionRegistryMixin 
implements QuarkusPlatformMixinSpec {
+
+    @CommandLine.Option(names = {
+            "--quarkus-group-id" }, description = "groupId of Quarkus Platform 
BOM; honored only if --quarkus-version is set",
+                        defaultValue = "io.quarkus.platform")
+    String quarkusGroupId = "io.quarkus.platform";
+
+    @CommandLine.Option(names = {
+            "--quarkus-artifact-id" },
+                        description = "Deprecated. This value is not used 
anymore. It is kept only for backwards compatibility and will be removed in 
Camel 5.x. Camel commands may use either 'quarkus-bom' or 'quarkus-camel-bom' 
artifactIds dependening on the context.",

Review Comment:
   Minor typo: `dependening` → `depending`.
   
   ```suggestion
                           description = "Deprecated. This value is not used 
anymore. It is kept only for backwards compatibility and will be removed in 
Camel 5.x. Camel commands may use either 'quarkus-bom' or 'quarkus-camel-bom' 
artifactIds depending on the context.",
   ```



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java:
##########
@@ -49,13 +54,12 @@ public class CamelUpdateMixin {
     boolean debug;
 

Review Comment:
   Same typo here: `Extensio Registry` → `Extension Registry` (missing 'n').
   
   ```suggestion
                           description = "The version of the Quarkus Maven 
plugin to use; the default value is looked up in Quarkus Extension Registry")
   ```



##########
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdateTest.java:
##########
@@ -96,11 +97,16 @@ private void checkOneDependencyAddedForArangoDb(RuntimeType 
rt) throws Exception
         }
     }
 
-    private void checkNoUpdateOnFreshlyGeneratedproject() throws Exception {
+    private void checkNoUpdateOnFreshlyGeneratedproject(RuntimeType rt) throws 
Exception {
         DependencyUpdate command = new DependencyUpdate(new 
CamelJBangMain().withPrinter(printer));
         CommandLine.populateCommand(command,
                 "--dir=" + workingDir,
+                "--camel-version=4.13.0",
+                CamelCommandBaseTestSupport.quarkusExtRegistry(),
                 new File(workingDir, "pom.xml").getAbsolutePath());
+        if (rt == RuntimeType.main) {

Review Comment:
   This looks like a debug leftover — `System.out.println("")` that prints an 
empty line conditionally. Consider removing it.



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java:
##########
@@ -16,150 +16,577 @@
  */
 package org.apache.camel.dsl.jbang.core.common;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
 import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.Properties;
 import java.util.function.BiConsumer;
-import java.util.function.BinaryOperator;
 import java.util.function.Function;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import 
org.apache.camel.dsl.jbang.core.commands.version.VersionList.CamelAndRuntimeVersions;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenDownloader;
+import org.apache.camel.tooling.maven.MavenGav;
+import org.apache.camel.util.json.DeserializationException;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_EXTENSION_REGISTRY_BASE_URI;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_GROUP_ID;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_VERSION;
 
 /**
  * Helper for resolving Quarkus platform information from the Quarkus registry.
  */
 public final class QuarkusHelper {
 
     public static final String QUARKUS_PLATFORM_URL_PROPERTY = 
"camel.jbang.quarkus.platform.url";
-    public static final String DEFAULT_QUARKUS_PLATFORM_URL = 
RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL
-                                                              + 
(RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL.endsWith("/")
-                                                                      ? "" : 
"/")
-                                                              + 
"client/platforms";
 
     private QuarkusHelper() {
     }
 
-    /**
-     * Returns the Quarkus platform registry URL, honoring the system property 
{@value #QUARKUS_PLATFORM_URL_PROPERTY}
-     * if set.
-     */
-    public static String getQuarkusPlatformUrl() {
-        return System.getProperty(QUARKUS_PLATFORM_URL_PROPERTY, 
DEFAULT_QUARKUS_PLATFORM_URL);
+    public static Stream<CamelAndRuntimeVersions> listQuarkusPlatformVersions(
+            Function<MavenGav, MavenArtifact> mavenResolver,
+            boolean download,
+            String quarkusExtensionRegistryBaseUri,
+            boolean fresh) {
+        JsonArray streams = 
fetchPlatformStreams(quarkusExtensionRegistryBaseUri, download, fresh, 
registriesDir());
+        if (streams == null || streams.isEmpty()) {
+            return Stream.empty();
+        }
+        return listQuarkusPlatformVersions(streams, mavenResolver);
     }
 
-    /**
-     * Resolves the actual Quarkus platform version for each row by fetching 
the Quarkus platform registry and matching
-     * the Camel Quarkus major.minor version against stream IDs.
-     *
-     * @param rows                 the list of rows to enrich with Quarkus 
platform versions
-     * @param runtimeVersionFunc   function to extract the runtime (Camel 
Quarkus) version from a row
-     * @param quarkusVersionSetter consumer to set the resolved Quarkus 
platform version on a row
-     */
-    public static <T> void resolveQuarkusPlatformVersions(
-            List<T> rows,
-            Function<T, String> runtimeVersionFunc,
-            BiConsumer<T, String> quarkusVersionSetter) {
-
-        JsonArray streams = fetchPlatformStreams();
-        if (streams == null) {
-            return;
+    private static Path registriesDir() {
+        final Path regDir = 
CommandLineHelper.getCamelDir().resolve("quarkus-extension-registries");
+        try {
+            if (!Files.isDirectory(regDir)) {
+                Files.createDirectories(regDir);
+            }
+            return regDir
+                    .toRealPath(LinkOption.NOFOLLOW_LINKS);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could not get real path for " + 
regDir, e);
         }
+    }
 
-        // keep the row with the highest runtime version per major.minor stream
-        BinaryOperator<T> keepLatest = (a, b) -> VersionHelper.compare(
-                runtimeVersionFunc.apply(a), runtimeVersionFunc.apply(b)) >= 0 
? a : b;
-
-        Map<String, T> latestPerStream = rows.stream()
-                .filter(row -> runtimeVersionFunc.apply(row) != null)
-                .collect(Collectors.toMap(
-                        row -> 
VersionHelper.getMajorMinorVersion(runtimeVersionFunc.apply(row)),
-                        Function.identity(),
-                        keepLatest));
-
-        // match each major.minor against registry streams and set the quarkus 
version
-        latestPerStream.forEach((majorMinor, row) -> 
findStreamVersion(streams, majorMinor, "quarkus-core-version")
-                .ifPresent(version -> quarkusVersionSetter.accept(row, 
version)));
+    static Stream<CamelAndRuntimeVersions> listQuarkusPlatformVersions(
+            JsonArray streams,
+            Function<MavenGav, MavenArtifact> mavenResolver) {
+        return streams.stream()
+                .map(s -> (JsonObject) s)
+                .map(PlatformStream::of)
+                .flatMap(platformStream -> 
platformStream.platformReleases().stream())
+                .map(platformRelease -> {
+                    List<String> versions
+                            = 
platformRelease.findManagedVersions(mavenResolver, Ga.CAMEL_DIRECT, 
Ga.CAMEL_QUARKUS_DIRECT);
+                    return new CamelAndRuntimeVersions(
+                            versions.get(0), 
platformRelease.quarkusCamelBomGav.getVersion(), versions.get(1));
+                });
     }
 
     /**
-     * Resolves the Quarkus platform BOM version by matching the build-time 
version's major.minor against the Quarkus
-     * platform registry.
+     * Finds the newest Quarkus platform BOM {@code g:a:v} using the same or 
newer {@code major.minor} Camel version as
+     * the specified {@code camelVersion} by searching in Quarkus platform 
registry or returns the specified
+     * {@code buildTimeQuarkusVersion} if no compatible Platform version can 
be found.
      * <p>
      * This is used by export/run commands to query the registry for the 
correct platform BOM version instead of using
      * the build-time constant. The registry may have a newer compatible 
version.
      *
-     * @param  buildTimeVersion the build-time Quarkus version (e.g., 
"3.15.7.something")
-     * @return                  the resolved platform BOM version from the 
registry, or the original buildTimeVersion if
-     *                          resolution fails
+     * @param  camelVersion                    if specified, the value of 
{@code --camel-version} CLI parameter or the
+     *                                         Camel version of the currently 
running camel-jbang. Must not be
+     *                                         {@code null}
+     * @param  quarkusExtensionRegistryBaseUri
+     * @param  downloader                      A {@link MavenDownloader} to 
use for accessing
+     * @return                                 the resolved platform BOM 
version from the registry, or the original
+     *                                         buildTimeVersion if resolution 
fails
      */
-    public static String resolveQuarkusPlatformVersion(String 
buildTimeVersion) {
-        if (buildTimeVersion == null) {
-            return null;
+    public static QuarkusPlatformBom findQuarkusPlatformBom(
+            String camelVersion,
+            Function<MavenGav, MavenArtifact> mavenResolver,
+            boolean download,
+            String quarkusExtensionRegistryBaseUri,
+            boolean fresh) {
+        if (camelVersion == null) {
+            camelVersion = RuntimeType.main.version();
         }
 
-        JsonArray streams = fetchPlatformStreams();
-        if (streams == null) {
-            return buildTimeVersion;
+        JsonArray streams = 
fetchPlatformStreams(quarkusExtensionRegistryBaseUri, download, fresh, 
registriesDir());
+        if (streams == null || streams.isEmpty()) {
+            return null;
         }
+        Optional<QuarkusPlatformBom> resolved
+                = findPlatformBom(streams, new MajorMinor(camelVersion), 
mavenResolver, quarkusExtensionRegistryBaseUri);
+        return resolved.orElse(null);
+    }
 
-        String majorMinor = 
VersionHelper.getMajorMinorVersion(buildTimeVersion);
-        Optional<String> resolved = findStreamVersion(streams, majorMinor, 
"version");
-        return resolved.orElse(buildTimeVersion);
+    public static String resolveCamelVersionFromQuarkusCamelBom(
+            MavenGav quarkusCamelBom, Function<MavenGav, MavenArtifact> 
mavenResolver) {
+        Path file = mavenResolver.apply(quarkusCamelBom).getFile().toPath();
+        XPath xPath = XPathFactory.newInstance().newXPath();
+        String expr = managedDependencyVersionXPath("org.apache.camel", 
"camel-direct");
+        try (InputStream in = Files.newInputStream(file)) {
+            String camelVersion = (String) xPath.evaluate(expr, new 
InputSource(in), XPathConstants.STRING);
+            return camelVersion;
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could not read " + file, e);
+        } catch (XPathExpressionException e) {
+            throw new RuntimeException("Could not evaluate " + expr + " on 
file " + file);
+        }
     }
 
     /**
      * Fetches the platform streams array from the Quarkus platform registry.
      *
      * @return the streams JsonArray, or null if the registry is unreachable 
or the response is invalid
      */
-    private static JsonArray fetchPlatformStreams() {
+    static JsonArray fetchPlatformStreams(
+            String quarkusExtensionRegistryBaseUri,
+            boolean download,
+            boolean fresh,
+            Path registriesDir) {
+        final String quarkusPlatformUrl = quarkusExtensionRegistryBaseUri
+                                          + "/client/platforms/all"
+                                          + 
(quarkusExtensionRegistryBaseUri.startsWith("file://") ? ".json" : "");
         try {
+            final URI uri = new URI(quarkusPlatformUrl);
+            if (uri.getScheme().equals("file")) {
+                return deserialize(Files.readString(Path.of(uri)));
+            }
+
+            if (fresh && !download) {
+                throw new IllegalStateException(
+                        "Options --fresh and --download=false contradict each 
other."
+                                                + " You may want to remove one 
of them or pass and explicit --quarkus-version if the current command supports 
it");
+            }
+
+            if (!fresh || !download) {
+                Path cacheFile = cacheFile(uri, registriesDir);
+                /* Check whether there is a cached document */
+                if (Files.isRegularFile(cacheFile)) {
+
+                    if (!download || updatedToday(cacheFile)) {
+                        /* We are in offline mode or the file was already 
updated today */
+                        return deserialize(Files.readString(cacheFile));
+                    }
+                }
+            }
+
             HttpClient hc = HttpClient.newHttpClient();
             HttpResponse<String> res = hc.send(
-                    HttpRequest.newBuilder(new URI(getQuarkusPlatformUrl()))
+                    HttpRequest.newBuilder(uri)
                             .timeout(Duration.ofSeconds(2))
                             .build(),
                     HttpResponse.BodyHandlers.ofString());
 
             if (res.statusCode() == 200) {
-                JsonObject json = (JsonObject) Jsoner.deserialize(res.body());
-                JsonArray platforms = json.getCollection("platforms");
-                if (platforms == null || platforms.isEmpty()) {
-                    return null;
-                }
-                JsonObject platform = platforms.getMap(0);
-                return platform.getCollection("streams");
+                final String jsonBody = res.body();
+
+                /* Refresh the cached file */
+                Path cacheFile = cacheFile(uri, registriesDir);
+                Files.createDirectories(cacheFile.getParent());
+                Files.writeString(cacheFile, jsonBody);
+
+                return deserialize(jsonBody);
+            } else {
+                throw new RuntimeException(
+                        "Could not get " + uri + ": " + res.statusCode() + " 
to determine the Quarkus Platform version."
+                                           + " You may want to pass 
--download=false to use the cached resource if available"
+                                           + " or pass and explicit 
--quarkus-version if the current command supports it");
             }
-        } catch (Exception e) {
-            // ignore - if the registry is not reachable within 2 seconds, 
return null
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted when fetching from " + 
quarkusPlatformUrl, e);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not fetch from " + 
quarkusPlatformUrl, e);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Unvalid URI " + quarkusPlatformUrl, e);

Review Comment:
   `Unvalid` is not a standard English word — should be `Invalid`.
   
   ```suggestion
               throw new RuntimeException("Invalid URI " + quarkusPlatformUrl, 
e);
           } catch (DeserializationException e) {
               throw new RuntimeException("Invalid JSON input from " + 
quarkusPlatformUrl, e);
   ```



##########
tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenGav.java:
##########
@@ -154,6 +154,25 @@ public static MavenGav parseGav(String gav, String 
defaultVersion) {
         return answer;
     }
 
+    /**
+     * @param  gav {@code 
<groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}
+     * @return     a new {@link MavenGav}
+     */
+    public static MavenGav parseMaveResolverString(String gaecv) {

Review Comment:
   Typo in public API method name: `parseMaveResolverString` — missing 'n' in 
"Maven". Should be `parseMavenResolverString`.
   
   Also, the `@param` Javadoc says `gav` but the actual parameter is named 
`gaecv` — these should match.
   
   ```suggestion
       public static MavenGav parseMavenResolverString(String gaecv) {
   ```



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportQuarkus.java:
##########
@@ -372,27 +394,44 @@ private void createMavenPom(Path settings, Path pom, 
Set<String> deps) throws Ex
         Files.writeString(pom, context);
     }
 
-    private List<Map<String, Object>> buildQuarkusDependencyList(Set<String> 
deps, CamelCatalog catalog) {
+    private List<Map<String, Object>> buildQuarkusDependencyList(Set<String> 
deps, MavenGav quarkusCamelBom) {
         List<MavenGav> gavs = new ArrayList<>();
-        for (String dep : deps) {
-            MavenGav gav = parseMavenGav(dep);
-            String gid = gav.getGroupId();
-            String aid = gav.getArtifactId();
-            // transform to camel-quarkus extension GAV
-            if ("org.apache.camel".equals(gid)) {
-                String qaid = aid.replace("camel-", "camel-quarkus-");
-                ArtifactModel<?> am = 
catalog.modelFromMavenGAV("org.apache.camel.quarkus", qaid, null);
-                if (am != null) {
-                    // use quarkus extension
-                    gav.setGroupId(am.getGroupId());
-                    gav.setArtifactId(am.getArtifactId());
-                    gav.setVersion(null); // uses BOM so version should not be 
included
-                } else {
-                    // there is no quarkus extension so use plain camel
-                    gav.setVersion(camelVersion);
+
+        Path file = 
mavenResolver.downloader().resolveArtifact(quarkusCamelBom).getFile().toPath();
+        DocumentBuilderFactory dbf = XmlHelper.createDocumentBuilderFactory();
+
+        try (InputStream in = Files.newInputStream(file)) {
+            DocumentBuilder db = dbf.newDocumentBuilder();
+            Document dom = db.parse(in);
+            XPath xPath = XPathFactory.newDefaultInstance().newXPath();
+            for (String dep : deps) {
+                MavenGav gav = parseMavenGav(dep);
+                String gid = gav.getGroupId();
+                String aid = gav.getArtifactId();
+                // transform to camel-quarkus extension GAV
+                if ("org.apache.camel".equals(gid)) {
+                    String qaid = aid.replace("camel-", "camel-quarkus-");
+                    if (nodeExists(file, dom, xPath, 
"org.apache.camel.quarkus", qaid)) {
+                        // use quarkus extension
+                        gav.setGroupId("org.apache.camel.quarkus");
+                        gav.setArtifactId(qaid);
+                        gav.setVersion(null); // uses BOM so version should 
not be included
+                    } else if (nodeExists(file, dom, xPath, 
"org.apache.camel", aid)) {
+                        // The Camel artifact is managed in Quarkus Camel BOM
+                        gav.setVersion(null);
+                    } else {
+                        // Use plain camel with hard-coded version
+                        gav.setVersion(camelVersion);
+                    }
                 }
+                gavs.add(gav);
             }
-            gavs.add(gav);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could not read " + file, e);
+        } catch (SAXException e) {
+            throw new RuntimeException("Could parse " + file, e);

Review Comment:
   Missing "not" in error messages — these currently read as if parsing 
succeeded:
   
   ```suggestion
               throw new RuntimeException("Could not parse " + file, e);
           } catch (ParserConfigurationException e) {
               throw new RuntimeException("Could not build a DocumentBuilder", 
e);
   ```



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java:
##########
@@ -0,0 +1,125 @@
+/*
+ * 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.util.Properties;
+import java.util.function.Function;
+
+import org.apache.camel.dsl.jbang.core.common.QuarkusHelper;
+import org.apache.camel.dsl.jbang.core.common.QuarkusHelper.QuarkusPlatformBom;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenDownloader;
+import org.apache.camel.tooling.maven.MavenGav;
+import picocli.CommandLine;
+
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_EXTENSION_REGISTRY_BASE_URI;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_GROUP_ID;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_VERSION;
+
+/**
+ * Options related to Quarkus Platform.
+ */
+public class QuarkusPlatformMixin extends QuarkusExtensionRegistryMixin 
implements QuarkusPlatformMixinSpec {
+
+    @CommandLine.Option(names = {
+            "--quarkus-group-id" }, description = "groupId of Quarkus Platform 
BOM; honored only if --quarkus-version is set",
+                        defaultValue = "io.quarkus.platform")
+    String quarkusGroupId = "io.quarkus.platform";
+
+    @CommandLine.Option(names = {
+            "--quarkus-artifact-id" },
+                        description = "Deprecated. This value is not used 
anymore. It is kept only for backwards compatibility and will be removed in 
Camel 5.x. Camel commands may use either 'quarkus-bom' or 'quarkus-camel-bom' 
artifactIds dependening on the context.",
+                        defaultValue = "quarkus-bom")
+    @Deprecated(forRemoval = true, since = "4.21.0")
+    String quarkusArtifactId = "quarkus-bom";
+
+    @CommandLine.Option(names = {
+            "--quarkus-version" },
+                        description = "version of Quarkus Platform BOM; the 
default value is looked up in Quarkus Extensio Registry")

Review Comment:
   Typo: `Extensio Registry` → `Extension Registry` (missing 'n').
   
   ```suggestion
                           description = "version of Quarkus Platform BOM; the 
default value is looked up in Quarkus Extension Registry")
   ```



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java:
##########
@@ -16,150 +16,577 @@
  */
 package org.apache.camel.dsl.jbang.core.common;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
 import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.Properties;
 import java.util.function.BiConsumer;
-import java.util.function.BinaryOperator;
 import java.util.function.Function;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import 
org.apache.camel.dsl.jbang.core.commands.version.VersionList.CamelAndRuntimeVersions;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.tooling.maven.MavenDownloader;
+import org.apache.camel.tooling.maven.MavenGav;
+import org.apache.camel.util.json.DeserializationException;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_EXTENSION_REGISTRY_BASE_URI;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_GROUP_ID;
+import static 
org.apache.camel.dsl.jbang.core.common.CamelJBangConstants.QUARKUS_VERSION;
 
 /**
  * Helper for resolving Quarkus platform information from the Quarkus registry.
  */
 public final class QuarkusHelper {
 
     public static final String QUARKUS_PLATFORM_URL_PROPERTY = 
"camel.jbang.quarkus.platform.url";
-    public static final String DEFAULT_QUARKUS_PLATFORM_URL = 
RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL
-                                                              + 
(RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL.endsWith("/")
-                                                                      ? "" : 
"/")
-                                                              + 
"client/platforms";
 
     private QuarkusHelper() {
     }
 
-    /**
-     * Returns the Quarkus platform registry URL, honoring the system property 
{@value #QUARKUS_PLATFORM_URL_PROPERTY}
-     * if set.
-     */
-    public static String getQuarkusPlatformUrl() {
-        return System.getProperty(QUARKUS_PLATFORM_URL_PROPERTY, 
DEFAULT_QUARKUS_PLATFORM_URL);
+    public static Stream<CamelAndRuntimeVersions> listQuarkusPlatformVersions(
+            Function<MavenGav, MavenArtifact> mavenResolver,
+            boolean download,
+            String quarkusExtensionRegistryBaseUri,
+            boolean fresh) {
+        JsonArray streams = 
fetchPlatformStreams(quarkusExtensionRegistryBaseUri, download, fresh, 
registriesDir());
+        if (streams == null || streams.isEmpty()) {
+            return Stream.empty();
+        }
+        return listQuarkusPlatformVersions(streams, mavenResolver);
     }
 
-    /**
-     * Resolves the actual Quarkus platform version for each row by fetching 
the Quarkus platform registry and matching
-     * the Camel Quarkus major.minor version against stream IDs.
-     *
-     * @param rows                 the list of rows to enrich with Quarkus 
platform versions
-     * @param runtimeVersionFunc   function to extract the runtime (Camel 
Quarkus) version from a row
-     * @param quarkusVersionSetter consumer to set the resolved Quarkus 
platform version on a row
-     */
-    public static <T> void resolveQuarkusPlatformVersions(
-            List<T> rows,
-            Function<T, String> runtimeVersionFunc,
-            BiConsumer<T, String> quarkusVersionSetter) {
-
-        JsonArray streams = fetchPlatformStreams();
-        if (streams == null) {
-            return;
+    private static Path registriesDir() {
+        final Path regDir = 
CommandLineHelper.getCamelDir().resolve("quarkus-extension-registries");
+        try {
+            if (!Files.isDirectory(regDir)) {
+                Files.createDirectories(regDir);
+            }
+            return regDir
+                    .toRealPath(LinkOption.NOFOLLOW_LINKS);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could not get real path for " + 
regDir, e);
         }
+    }
 
-        // keep the row with the highest runtime version per major.minor stream
-        BinaryOperator<T> keepLatest = (a, b) -> VersionHelper.compare(
-                runtimeVersionFunc.apply(a), runtimeVersionFunc.apply(b)) >= 0 
? a : b;
-
-        Map<String, T> latestPerStream = rows.stream()
-                .filter(row -> runtimeVersionFunc.apply(row) != null)
-                .collect(Collectors.toMap(
-                        row -> 
VersionHelper.getMajorMinorVersion(runtimeVersionFunc.apply(row)),
-                        Function.identity(),
-                        keepLatest));
-
-        // match each major.minor against registry streams and set the quarkus 
version
-        latestPerStream.forEach((majorMinor, row) -> 
findStreamVersion(streams, majorMinor, "quarkus-core-version")
-                .ifPresent(version -> quarkusVersionSetter.accept(row, 
version)));
+    static Stream<CamelAndRuntimeVersions> listQuarkusPlatformVersions(
+            JsonArray streams,
+            Function<MavenGav, MavenArtifact> mavenResolver) {
+        return streams.stream()
+                .map(s -> (JsonObject) s)
+                .map(PlatformStream::of)
+                .flatMap(platformStream -> 
platformStream.platformReleases().stream())
+                .map(platformRelease -> {
+                    List<String> versions
+                            = 
platformRelease.findManagedVersions(mavenResolver, Ga.CAMEL_DIRECT, 
Ga.CAMEL_QUARKUS_DIRECT);
+                    return new CamelAndRuntimeVersions(
+                            versions.get(0), 
platformRelease.quarkusCamelBomGav.getVersion(), versions.get(1));
+                });
     }
 
     /**
-     * Resolves the Quarkus platform BOM version by matching the build-time 
version's major.minor against the Quarkus
-     * platform registry.
+     * Finds the newest Quarkus platform BOM {@code g:a:v} using the same or 
newer {@code major.minor} Camel version as
+     * the specified {@code camelVersion} by searching in Quarkus platform 
registry or returns the specified
+     * {@code buildTimeQuarkusVersion} if no compatible Platform version can 
be found.
      * <p>
      * This is used by export/run commands to query the registry for the 
correct platform BOM version instead of using
      * the build-time constant. The registry may have a newer compatible 
version.
      *
-     * @param  buildTimeVersion the build-time Quarkus version (e.g., 
"3.15.7.something")
-     * @return                  the resolved platform BOM version from the 
registry, or the original buildTimeVersion if
-     *                          resolution fails
+     * @param  camelVersion                    if specified, the value of 
{@code --camel-version} CLI parameter or the
+     *                                         Camel version of the currently 
running camel-jbang. Must not be
+     *                                         {@code null}
+     * @param  quarkusExtensionRegistryBaseUri
+     * @param  downloader                      A {@link MavenDownloader} to 
use for accessing
+     * @return                                 the resolved platform BOM 
version from the registry, or the original
+     *                                         buildTimeVersion if resolution 
fails
      */
-    public static String resolveQuarkusPlatformVersion(String 
buildTimeVersion) {
-        if (buildTimeVersion == null) {
-            return null;
+    public static QuarkusPlatformBom findQuarkusPlatformBom(
+            String camelVersion,
+            Function<MavenGav, MavenArtifact> mavenResolver,
+            boolean download,
+            String quarkusExtensionRegistryBaseUri,
+            boolean fresh) {
+        if (camelVersion == null) {
+            camelVersion = RuntimeType.main.version();
         }
 
-        JsonArray streams = fetchPlatformStreams();
-        if (streams == null) {
-            return buildTimeVersion;
+        JsonArray streams = 
fetchPlatformStreams(quarkusExtensionRegistryBaseUri, download, fresh, 
registriesDir());
+        if (streams == null || streams.isEmpty()) {
+            return null;
         }
+        Optional<QuarkusPlatformBom> resolved
+                = findPlatformBom(streams, new MajorMinor(camelVersion), 
mavenResolver, quarkusExtensionRegistryBaseUri);
+        return resolved.orElse(null);
+    }
 
-        String majorMinor = 
VersionHelper.getMajorMinorVersion(buildTimeVersion);
-        Optional<String> resolved = findStreamVersion(streams, majorMinor, 
"version");
-        return resolved.orElse(buildTimeVersion);
+    public static String resolveCamelVersionFromQuarkusCamelBom(
+            MavenGav quarkusCamelBom, Function<MavenGav, MavenArtifact> 
mavenResolver) {
+        Path file = mavenResolver.apply(quarkusCamelBom).getFile().toPath();
+        XPath xPath = XPathFactory.newInstance().newXPath();
+        String expr = managedDependencyVersionXPath("org.apache.camel", 
"camel-direct");
+        try (InputStream in = Files.newInputStream(file)) {
+            String camelVersion = (String) xPath.evaluate(expr, new 
InputSource(in), XPathConstants.STRING);
+            return camelVersion;
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could not read " + file, e);
+        } catch (XPathExpressionException e) {
+            throw new RuntimeException("Could not evaluate " + expr + " on 
file " + file);
+        }
     }
 
     /**
      * Fetches the platform streams array from the Quarkus platform registry.
      *
      * @return the streams JsonArray, or null if the registry is unreachable 
or the response is invalid
      */
-    private static JsonArray fetchPlatformStreams() {
+    static JsonArray fetchPlatformStreams(
+            String quarkusExtensionRegistryBaseUri,
+            boolean download,
+            boolean fresh,
+            Path registriesDir) {
+        final String quarkusPlatformUrl = quarkusExtensionRegistryBaseUri
+                                          + "/client/platforms/all"
+                                          + 
(quarkusExtensionRegistryBaseUri.startsWith("file://") ? ".json" : "");
         try {
+            final URI uri = new URI(quarkusPlatformUrl);
+            if (uri.getScheme().equals("file")) {
+                return deserialize(Files.readString(Path.of(uri)));
+            }
+
+            if (fresh && !download) {
+                throw new IllegalStateException(
+                        "Options --fresh and --download=false contradict each 
other."
+                                                + " You may want to remove one 
of them or pass and explicit --quarkus-version if the current command supports 
it");
+            }
+
+            if (!fresh || !download) {
+                Path cacheFile = cacheFile(uri, registriesDir);
+                /* Check whether there is a cached document */
+                if (Files.isRegularFile(cacheFile)) {
+
+                    if (!download || updatedToday(cacheFile)) {
+                        /* We are in offline mode or the file was already 
updated today */
+                        return deserialize(Files.readString(cacheFile));
+                    }
+                }
+            }
+
             HttpClient hc = HttpClient.newHttpClient();
             HttpResponse<String> res = hc.send(
-                    HttpRequest.newBuilder(new URI(getQuarkusPlatformUrl()))
+                    HttpRequest.newBuilder(uri)
                             .timeout(Duration.ofSeconds(2))
                             .build(),
                     HttpResponse.BodyHandlers.ofString());
 
             if (res.statusCode() == 200) {
-                JsonObject json = (JsonObject) Jsoner.deserialize(res.body());
-                JsonArray platforms = json.getCollection("platforms");
-                if (platforms == null || platforms.isEmpty()) {
-                    return null;
-                }
-                JsonObject platform = platforms.getMap(0);
-                return platform.getCollection("streams");
+                final String jsonBody = res.body();
+
+                /* Refresh the cached file */
+                Path cacheFile = cacheFile(uri, registriesDir);
+                Files.createDirectories(cacheFile.getParent());
+                Files.writeString(cacheFile, jsonBody);
+
+                return deserialize(jsonBody);
+            } else {
+                throw new RuntimeException(
+                        "Could not get " + uri + ": " + res.statusCode() + " 
to determine the Quarkus Platform version."
+                                           + " You may want to pass 
--download=false to use the cached resource if available"
+                                           + " or pass and explicit 
--quarkus-version if the current command supports it");
             }
-        } catch (Exception e) {
-            // ignore - if the registry is not reachable within 2 seconds, 
return null
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted when fetching from " + 
quarkusPlatformUrl, e);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not fetch from " + 
quarkusPlatformUrl, e);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Unvalid URI " + quarkusPlatformUrl, e);
+        } catch (DeserializationException e) {
+            throw new RuntimeException("Unvalid JSON input from " + 
quarkusPlatformUrl, e);
+        }
+    }
+
+    private static boolean updatedToday(Path cacheFile) {
+        try {
+            FileTime lastModified = Files.getLastModifiedTime(cacheFile);
+            final ZoneId zone = ZoneId.systemDefault();
+            final Instant startOfToday = LocalDate.now(zone)
+                    .atStartOfDay(zone)
+                    .toInstant();
+
+            return !lastModified.toInstant().isBefore(startOfToday);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Could get last modified time of " 
+ cacheFile, e);

Review Comment:
   Missing "not" — should read "Could **not** get last modified time".
   
   ```suggestion
               throw new UncheckedIOException("Could not get last modified time 
of " + cacheFile, e);
   ```



-- 
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]


Reply via email to