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

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


The following commit(s) were added to refs/heads/main by this push:
     new c7eaecba6f48 CAMEL-24809: camel-kamelet-main - known dependencies 
mapped by package, generated from a curated list
c7eaecba6f48 is described below

commit c7eaecba6f4886845e458ec610dd1d8edfafa7a4
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 13:53:07 2026 +0200

    CAMEL-24809: camel-kamelet-main - known dependencies mapped by package, 
generated from a curated list
    
    camel run resolves the class of a bean or a property to a Maven dependency 
through the known
    dependencies of camel-kamelet-main and downloads it on demand. The mapping 
listed a handful of
    classes one by one. It now maps third-party libraries by package, one line 
per library, from a
    curated input file (src/main/known-third-party-libraries.properties, 146 
libraries: JDBC drivers
    and pools, messaging clients, databases and search, vector databases, AWS, 
Azure, Google Cloud,
    Kubernetes, SaaS clients, data formats, templates, network, security, 
observability, AI SDKs).
    The prepare-kamelet-main mojo resolves every version from camel-parent's 
properties or a BOM at
    build time, fails on a missing property or a literal version, and writes 
the resolved file into
    target/classes; -Dcamel.known-dependencies.verify=true also checks each 
package against its jar.
    The four literal driver versions became parent properties. The user manual 
gains an advanced
    section on how a class becomes a dependency.
    
    Closes #26575
---
 .../modules/ROOT/pages/camel-jbang-running.adoc    |  50 ++++-
 .../main/download/KnownDependenciesResolver.java   |   2 +
 .../main/known-third-party-libraries.properties    | 212 ++++++++++++++++++++
 .../camel-main-known-dependencies.properties       |  16 +-
 .../download/KnownDependenciesResolverTest.java    |  38 ++++
 parent/pom.xml                                     |   4 +
 .../maven/packaging/PrepareKameletMainMojo.java    | 222 ++++++++++++++++++++-
 7 files changed, 530 insertions(+), 14 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
index a835b0948e79..5ef0ed5f678f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
@@ -130,8 +130,10 @@ It does not support `camel-servlet` or `camel-jetty`.
 
 == Adding custom JARs
 
-Camel CLI automatically detects and downloads dependencies for Camel 
components.
-For 3rd-party JARs, use `--dep` with Maven GAV syntax:
+Camel CLI automatically detects and downloads dependencies for Camel 
components, and for the
+well-known third-party libraries a route may name in a bean, such as a JDBC 
datasource or a JMS
+connection factory (see <<advanced-class-to-dependency,how a class becomes a 
dependency>>).
+For other 3rd-party JARs, use `--dep` with Maven GAV syntax:
 
 [source,bash]
 ----
@@ -234,6 +236,50 @@ To disable automatic downloading:
 camel run foo.java --download=false
 ----
 
+[[advanced-class-to-dependency]]
+=== Advanced: how a class becomes a dependency
+
+When a route, a bean declaration, a `#class:` value in 
`application.properties`, or an `import`
+in a Java file next to the route names a class that is not on the classpath, 
Camel CLI looks the
+class up in three mapping files shipped in `camel-kamelet-main` and downloads 
the dependency it
+maps to. The files are read into one table, in this order (a later file wins 
for an identical key):
+
+1. `camel-main-known-dependencies.properties`, hand-written: Spring and 
Quarkus annotations,
+   `camel.*` switches, the LangChain4j model providers.
+2. `camel-component-known-dependencies.properties`, generated from the 
catalog: every Camel
+   component class maps to its `camel-` artifact.
+3. `camel-thirdparty-known-dependencies.properties`, generated at build time 
from the curated
+   list `known-third-party-libraries.properties` in `camel-kamelet-main`: JDBC 
drivers and
+   connection pools, messaging clients, cloud SDKs, JSON, XML and CSV 
libraries, and other
+   libraries a route commonly names, each mapped by package.
+
+The lookup walks up from the class name:
+
+1. The exact class, for example `org.postgresql.ds.PGSimpleDataSource`.
+2. Each enclosing package in turn, `org.postgresql.ds`, then `org.postgresql`, 
until a key
+   matches or no package is left. The deepest key wins, so 
`org.apache.activemq.artemis` is found
+   before `org.apache.activemq`.
+3. The value is a Maven coordinate. A `camel:xxx` short form becomes 
`org.apache.camel:camel-xxx`
+   at the running Camel version; a `${...}` placeholder in the hand-written 
file is resolved
+   from the `camel-dependencies` POM of that version; the generated 
third-party file carries the
+   versions already resolved.
+4. The dependency and its transitive dependencies are downloaded and added to 
the classpath,
+   and the class is loaded again.
+
+A dependency you declare yourself, with `--dep`, `camel.jbang.dependencies`, 
or in the project's
+POM, is on the classpath before any lookup, so it always takes precedence. Use 
that when a
+mapped library is not the one you want, for example the ActiveMQ 5 client 
where the mapping
+picks the ActiveMQ 6 client.
+
+`camel validate` and the write tools of the Camel MCP server consult the same 
three files, so a
+bean whose class Camel CLI would download is not reported as missing.
+
+To add a library to the mapping, add one line to 
`known-third-party-libraries.properties`
+in `camel-kamelet-main`: the library's own package, its `groupId:artifactId`, 
and a version
+property of `camel-parent`. The build resolves the version and fails on a 
property that does
+not exist; with `-Dcamel.known-dependencies.verify=true` it also resolves 
every JAR and checks
+that the package is in it.
+
 == Runtimes
 
 By default `camel run` runs the integration in-process, inside the JVM of the 
Camel CLI itself.
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
index cae1d090987a..79f8999be675 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
@@ -42,6 +42,8 @@ public final class KnownDependenciesResolver {
     public void loadKnownDependencies() {
         doLoadKnownDependencies("camel-main-known-dependencies.properties");
         
doLoadKnownDependencies("camel-component-known-dependencies.properties");
+        // third-party libraries mapped by package, generated from 
src/main/known-third-party-libraries.properties
+        
doLoadKnownDependencies("camel-thirdparty-known-dependencies.properties");
     }
 
     public void loadKnownFactoryFinderDependencies() {
diff --git 
a/dsl/camel-kamelet-main/src/main/known-third-party-libraries.properties 
b/dsl/camel-kamelet-main/src/main/known-third-party-libraries.properties
new file mode 100644
index 000000000000..dce610efc6d9
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/main/known-third-party-libraries.properties
@@ -0,0 +1,212 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+# Third-party libraries camel run downloads on demand when a bean, a property 
or a Java import names one of
+# their classes (CAMEL-24809). The input of 
camel-package-maven-plugin:prepare-kamelet-main, which generates
+# camel-thirdparty-known-dependencies.properties into target/classes with the 
versions resolved; that file is
+# shipped in the jar and never committed, this one is the source.
+#
+# One line per library: <package> = <groupId>:<artifactId>:<version>
+#   <version> is ${a-version-property} of parent/pom.xml (checked at build 
time; a literal version is refused),
+#   or @bom(<groupId>:<artifactId>:${bom-version-property}) for a library 
whose version a BOM manages.
+# The runtime matches the class name and then each enclosing package, so map 
the library's own package and
+# never a shared parent such as org.apache.commons or com.google.cloud, or the 
walk up the package picks the
+# wrong jar. With -Dcamel.known-dependencies.verify=true the build resolves 
every jar and checks the package.
+
+# JDBC drivers, datasources and connection pools
+org.postgresql = org.postgresql:postgresql:${pgjdbc-driver-version}
+com.mysql.cj = com.mysql:mysql-connector-j:${debezium-mysql-connector-version}
+org.mariadb.jdbc = org.mariadb.jdbc:mariadb-java-client:${mariadb-version}
+com.microsoft.sqlserver.jdbc = 
com.microsoft.sqlserver:mssql-jdbc:${mssql-jdbc-version}
+oracle.jdbc = com.oracle.database.jdbc:ojdbc17:${ojdbc-version}
+com.amazon.redshift = 
com.amazon.redshift:redshift-jdbc42:${redshift-jdbc-version}
+org.h2 = com.h2database:h2:${h2-version}
+org.hsqldb = org.hsqldb:hsqldb:${hsqldb-version}
+com.clickhouse.client = com.clickhouse:client-v2:${clickhouse-client-version}
+com.zaxxer.hikari = com.zaxxer:HikariCP:${hikaricp-version}
+com.mchange.v2.c3p0 = com.mchange:c3p0:${c3p0-version}
+org.apache.commons.dbcp2 = 
org.apache.commons:commons-dbcp2:${commons-dbcp2-version}
+
+# ORM, SQL and scheduling
+org.hibernate = org.hibernate.orm:hibernate-core:${hibernate-version}
+org.hibernate.validator = 
org.hibernate.validator:hibernate-validator:${hibernate-validator-version}
+org.jooq = org.jooq:jooq:${jooq-version}
+org.apache.ibatis = org.mybatis:mybatis:${mybatis-version}
+org.quartz = org.quartz-scheduler:quartz:${quartz-version}
+
+# Messaging clients. The Artemis package is deeper than the classic ActiveMQ 
one and is matched first. The classic
+# ActiveMQ 5 and 6 clients share the org.apache.activemq package and only one 
can be mapped: it is the ActiveMQ 6
+# client, the Jakarta one Camel 4 needs, which speaks OpenWire to 5.x brokers 
too. A project that must use the 5.x
+# Jakarta client declares org.apache.activemq:activemq-client-jakarta itself; 
a declared dependency puts the class
+# on the classpath and no download is attempted.
+org.apache.activemq.artemis = 
org.apache.activemq:artemis-jakarta-client-all:${activemq-artemis-version}
+org.apache.activemq = org.apache.activemq:activemq-client:${activemq6-version}
+org.apache.qpid.jms = 
org.apache.qpid:qpid-jms-client:${qpid-jms-client-version}
+org.messaginghub.pooled.jms = org.messaginghub:pooled-jms:${pooled-jms-version}
+com.ibm.mq = 
com.ibm.mq:com.ibm.mq.jakarta.client:${com-ibm-mq-jakarta-client-version}
+org.springframework.amqp = 
org.springframework.amqp:spring-rabbit:${spring-rabbitmq-version}
+org.apache.kafka.clients = org.apache.kafka:kafka-clients:${kafka-version}
+org.apache.kafka.common = org.apache.kafka:kafka-clients:${kafka-version}
+org.apache.pulsar.client = org.apache.pulsar:pulsar-client:${pulsar-version}
+org.apache.rocketmq = org.apache.rocketmq:rocketmq-client:${rocketmq-version}
+io.nats.client = io.nats:jnats:${jnats-version}
+org.eclipse.paho.client.mqttv3 = 
org.eclipse.paho:org.eclipse.paho.client.mqttv3:${paho-version}
+org.eclipse.paho.mqttv5.client = 
org.eclipse.paho:org.eclipse.paho.mqttv5.client:${paho-version}
+com.hivemq.client = com.hivemq:hivemq-mqtt-client:${hivemq-mqtt-client-version}
+
+# Databases, search and caches
+com.mongodb = org.mongodb:mongodb-driver-sync:${mongo-java-driver-version}
+redis.clients.jedis = redis.clients:jedis:${jedis-client-version}
+com.datastax.oss.driver = 
org.apache.cassandra:java-driver-core:${cassandra-driver-version}
+com.couchbase.client = 
com.couchbase.client:java-client:${couchbase-client-version}
+org.neo4j.driver = org.neo4j.driver:neo4j-java-driver:${neo4j-version}
+co.elastic.clients = 
co.elastic.clients:elasticsearch-java:${elasticsearch-java-client-version}
+org.elasticsearch.client = 
org.elasticsearch.client:elasticsearch-rest-client:${elasticsearch-java-client-version}
+org.opensearch.client = 
org.opensearch.client:opensearch-java:${opensearch-java-client-version}
+org.apache.solr.client.solrj = org.apache.solr:solr-solrj:${solr-version}
+org.apache.lucene = org.apache.lucene:lucene-core:${lucene-version}
+org.apache.ignite = org.apache.ignite:ignite-core:${ignite-version}
+com.github.benmanes.caffeine = 
com.github.ben-manes.caffeine:caffeine:${caffeine-version}
+com.hazelcast = com.hazelcast:hazelcast:${hazelcast-version}
+org.infinispan.client.hotrod = 
org.infinispan:infinispan-client-hotrod:${infinispan-version}
+org.infinispan = org.infinispan:infinispan-core:${infinispan-version}
+org.ehcache = org.ehcache:ehcache:${ehcache3-version}
+org.jgroups = org.jgroups:jgroups:${jgroups-version}
+
+# Vector databases
+io.milvus = io.milvus:milvus-sdk-java:${milvus-client-version}
+io.qdrant.client = io.qdrant:client:${qdrant-client-version}
+io.weaviate.client6 = io.weaviate:client6:${weaviate-client-version}
+io.pinecone = io.pinecone:pinecone-client:${pinecone-client-version}
+
+# Cloud: AWS SDK v2, one artifact per service
+software.amazon.awssdk.services.s3 = 
software.amazon.awssdk:s3:${aws-java-sdk2-version}
+software.amazon.awssdk.services.sqs = 
software.amazon.awssdk:sqs:${aws-java-sdk2-version}
+software.amazon.awssdk.services.sns = 
software.amazon.awssdk:sns:${aws-java-sdk2-version}
+software.amazon.awssdk.services.dynamodb = 
software.amazon.awssdk:dynamodb:${aws-java-sdk2-version}
+software.amazon.awssdk.services.kinesis = 
software.amazon.awssdk:kinesis:${aws-java-sdk2-version}
+software.amazon.awssdk.services.lambda = 
software.amazon.awssdk:lambda:${aws-java-sdk2-version}
+software.amazon.awssdk.services.secretsmanager = 
software.amazon.awssdk:secretsmanager:${aws-java-sdk2-version}
+software.amazon.awssdk.services.eventbridge = 
software.amazon.awssdk:eventbridge:${aws-java-sdk2-version}
+software.amazon.awssdk.services.ses = 
software.amazon.awssdk:ses:${aws-java-sdk2-version}
+
+# Cloud: Azure, versions managed by the Azure SDK BOM
+com.azure.storage.blob = 
com.azure:azure-storage-blob:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.storage.queue = 
com.azure:azure-storage-queue:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.storage.file.share = 
com.azure:azure-storage-file-share:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.storage.file.datalake = 
com.azure:azure-storage-file-datalake:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.messaging.servicebus = 
com.azure:azure-messaging-servicebus:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.messaging.eventhubs = 
com.azure:azure-messaging-eventhubs:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.messaging.eventgrid = 
com.azure:azure-messaging-eventgrid:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.cosmos = 
com.azure:azure-cosmos:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.identity = 
com.azure:azure-identity:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+com.azure.security.keyvault.secrets = 
com.azure:azure-security-keyvault-secrets:@bom(com.azure:azure-sdk-bom:${azure-sdk-bom-version})
+
+# Cloud: Google
+com.google.cloud.storage = 
com.google.cloud:google-cloud-storage:${google-cloud-storage-version}
+com.google.cloud.pubsub = 
com.google.cloud:google-cloud-pubsub:${google-cloud-pubsub-version}
+com.google.cloud.bigquery = 
com.google.cloud:google-cloud-bigquery:${google-cloud-bigquery-version}
+com.google.cloud.firestore = 
com.google.cloud:google-cloud-firestore:${google-cloud-firestore-version}
+com.google.cloud.secretmanager = 
com.google.cloud:google-cloud-secretmanager:${google-cloud-secretmanager-version}
+com.google.cloud.functions = 
com.google.cloud:google-cloud-functions:${google-cloud-functions-version}
+com.google.genai = com.google.genai:google-genai:${google-genai-version}
+
+# Cloud: Kubernetes, object storage, service discovery
+io.fabric8.kubernetes.client = 
io.fabric8:kubernetes-client:${kubernetes-client-version}
+io.fabric8.openshift.client = 
io.fabric8:openshift-client:${kubernetes-client-version}
+io.minio = io.minio:minio:${minio-version}
+org.kiwiproject.consul = org.kiwiproject:consul-client:${consul-client-version}
+org.apache.zookeeper = org.apache.zookeeper:zookeeper:${zookeeper-version}
+org.springframework.vault = 
org.springframework.vault:spring-vault-core:${spring-vault-core-version}
+
+# SaaS clients
+com.slack.api = com.slack.api:slack-api-client:${slack-api-model-version}
+com.twilio = com.twilio.sdk:twilio:${twilio-version}
+com.stripe = com.stripe:stripe-java:${stripe-java-version}
+
+# Data: JSON, YAML, CSV, XML, binary formats and transformations
+com.fasterxml.jackson.databind = 
com.fasterxml.jackson.core:jackson-databind:${jackson2-version}
+com.fasterxml.jackson.dataformat.xml = 
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2-version}
+com.fasterxml.jackson.dataformat.yaml = 
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jackson2-version}
+com.fasterxml.jackson.dataformat.csv = 
com.fasterxml.jackson.dataformat:jackson-dataformat-csv:${jackson2-version}
+com.google.gson = com.google.code.gson:gson:${gson-version}
+com.alibaba.fastjson = com.alibaba:fastjson:${fastjson-version}
+org.eclipse.yasson = org.eclipse:yasson:${yasson-version}
+org.yaml.snakeyaml = org.yaml:snakeyaml:${snakeyaml-version}
+org.apache.commons.csv = org.apache.commons:commons-csv:${commons-csv-version}
+com.univocity.parsers = 
com.univocity:univocity-parsers:${univocity-parsers-version}
+org.apache.avro = org.apache.avro:avro:${avro-version}
+com.google.protobuf = com.google.protobuf:protobuf-java:${protobuf-version}
+org.apache.thrift = org.apache.thrift:libthrift:${libthrift-version}
+com.bazaarvoice.jolt = com.bazaarvoice.jolt:jolt-core:${jolt-version}
+com.schibsted.spt.data.jslt = com.schibsted.spt.data:jslt:${jslt-version}
+net.thisptr.jackson.jq = net.thisptr:jackson-jq:${jackson-jq-version}
+org.xmlunit = org.xmlunit:xmlunit-core:${xmlunit-version}
+
+# Commons and utilities
+org.apache.commons.lang3 = 
org.apache.commons:commons-lang3:${commons-lang3-version}
+org.apache.commons.io = commons-io:commons-io:${commons-io-version}
+com.google.common = com.google.guava:guava:${guava-version}
+org.jsoup = org.jsoup:jsoup:${jsoup-version}
+
+# Templates and documents
+freemarker = org.freemarker:freemarker:${freemarker-version}
+org.apache.velocity = 
org.apache.velocity:velocity-engine-core:${velocity-version}
+org.thymeleaf = org.thymeleaf:thymeleaf:${thymeleaf-version}
+com.github.mustachejava = 
com.github.spullara.mustache.java:compiler:${mustache-java-version}
+org.apache.pdfbox = org.apache.pdfbox:pdfbox:${pdfbox-version}
+org.apache.tika = org.apache.tika:tika-core:${tika-version}
+
+# HTTP, network and reactive
+org.apache.hc.client5 = 
org.apache.httpcomponents.client5:httpclient5:${httpclient-version}
+org.apache.hc.core5 = 
org.apache.httpcomponents.core5:httpcore5:${httpcore-version}
+io.vertx.core = io.vertx:vertx-core:${vertx-version}
+io.vertx.ext.web = io.vertx:vertx-web:${vertx-version}
+io.undertow = io.undertow:undertow-core:${undertow-version}
+io.grpc.stub = io.grpc:grpc-stub:${grpc-version}
+io.grpc.protobuf = io.grpc:grpc-protobuf:${grpc-version}
+io.grpc.netty = io.grpc:grpc-netty-shaded:${grpc-version}
+reactor.core = io.projectreactor:reactor-core:${reactor-version}
+io.reactivex = io.reactivex.rxjava2:rxjava:${rxjava2-version}
+org.apache.mina = org.apache.mina:mina-core:${mina-version}
+org.apache.sshd = org.apache.sshd:sshd-core:${sshd-version}
+com.jcraft.jsch = com.github.mwiede:jsch:${jsch-version}
+com.hierynomus.smbj = com.hierynomus:smbj:${smbj-version}
+org.eclipse.angus.mail = org.eclipse.angus:angus-mail:${angus-mail-version}
+
+# Security
+org.bouncycastle.openpgp = 
org.bouncycastle:bcpg-jdk18on:${bouncycastle-version}
+org.bouncycastle.bcpg = org.bouncycastle:bcpg-jdk18on:${bouncycastle-version}
+org.bouncycastle.cms = org.bouncycastle:bcpkix-jdk18on:${bouncycastle-version}
+org.bouncycastle.cert = org.bouncycastle:bcpkix-jdk18on:${bouncycastle-version}
+org.bouncycastle.mail = org.bouncycastle:bcmail-jdk18on:${bouncycastle-version}
+org.bouncycastle = org.bouncycastle:bcprov-jdk18on:${bouncycastle-version}
+org.keycloak.admin.client = 
org.keycloak:keycloak-admin-client:${keycloak-client-version}
+org.jasypt = org.jasypt:jasypt:${jasypt-version}
+
+# Observability and scripting
+io.micrometer = io.micrometer:micrometer-core:${micrometer-version}
+io.opentelemetry.api = 
io.opentelemetry:opentelemetry-api:${opentelemetry-version}
+groovy = org.apache.groovy:groovy:${groovy-version}
+org.apache.groovy = org.apache.groovy:groovy:${groovy-version}
+org.codehaus.groovy = org.apache.groovy:groovy:${groovy-version}
+
+# AI (the model providers are mapped in 
camel-main-known-dependencies.properties; this is the core and the SDKs)
+dev.langchain4j = dev.langchain4j:langchain4j:${langchain4j-version}
+com.openai.client.okhttp = 
com.openai:openai-java-client-okhttp:${openai-java-version}
+com.openai = com.openai:openai-java-core:${openai-java-version}
+ai.docling.serve = ai.docling:docling-serve-client:${docling-java-version}
diff --git 
a/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
 
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
index 7a43b2dc61bf..2f1b4675f4f9 100644
--- 
a/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
+++ 
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
@@ -57,22 +57,11 @@ META-INF/services/org/apache/camel/modelyaml-dumper = 
camel:yaml-io
 META-INF/services/org/apache/camel/modeljava-dumper = camel:java-io
 META-INF/services/org/apache/camel/cron/cron-service = camel:quartz
 
-com.amazon.redshift.jdbc.Driver = com.amazon.redshift:redshift-jdbc42:2.1.0.33
-com.microsoft.sqlserver.jdbc.SQLServerDriver = 
com.microsoft.sqlserver:mssql-jdbc:12.10.0.jre11
-com.mysql.cj.jdbc.Driver = 
com.mysql:mysql-connector-j:${debezium-mysql-connector-version}
-com.zaxxer.hikari.HikariDataSource = com.zaxxer:HikariCP:6.3.0
 net.sf.saxon.xpath.XPathFactoryImpl = camel:saxon
-oracle.jdbc.driver.OracleDriver = com.oracle.database.jdbc:ojdbc17:23.8.0.25.04
-org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory = 
org.apache.activemq:artemis-jakarta-client-all:${activemq-artemis-version}
 org.apache.camel.component.activemq.ActiveMQComponent\:embedded\=true = 
org.apache.activemq:activemq-broker:${activemq-version}
 org.apache.camel.component.activemq6.ActiveMQComponent\:embedded\=true = 
org.apache.activemq:activemq-broker:${activemq6-version}
 org.apache.camel.component.cxf.jaxrs.CxfRsEndpoint = camel:cxf-rest
 org.apache.camel.component.cxf.jaxws.CxfEndpoint = camel:cxf-soap
-org.apache.commons.dbcp2.BasicDataSource = 
org.apache.commons:commons-dbcp2:${commons-dbcp2-version}
-org.apache.qpid.jms.JmsConnectionFactory = 
org.apache.qpid:qpid-jms-client:${qpid-jms-client-version}
-org.messaginghub.pooled.jms.JmsPoolConnectionFactory = 
org.messaginghub:pooled-jms:${pooled-jms-version}
-org.postgresql.Driver = org.postgresql:postgresql:${pgjdbc-driver-version}
-org.postgresql.ds.PGSimpleDataSource = 
org.postgresql:postgresql:${pgjdbc-driver-version}
 
 dev.langchain4j.model.ollama = 
dev.langchain4j:langchain4j-ollama:${langchain4j-version}
 dev.langchain4j.model.openai = 
dev.langchain4j:langchain4j-open-ai:${langchain4j-version}
@@ -88,3 +77,8 @@ dev.langchain4j.model.embedding.onnx = 
dev.langchain4j:langchain4j-embeddings:${
 org.apache.camel.component.ai.observability.GenAiObservabilityImpl = 
camel:ai-observability
 # camel-main property prefix (same pattern as camel.opentelemetry) — resolves 
ai-observability when GenAI observability config is accessed
 camel.aiObservability = camel:ai-observability
+
+# Third-party libraries (JDBC drivers, clients, SDKs) are mapped by package in
+# src/main/known-third-party-libraries.properties, generated with the versions 
resolved into
+# target/classes/camel-thirdparty-known-dependencies.properties by 
camel-package-maven-plugin:prepare-kamelet-main
+# (CAMEL-24809).
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
index 3eba6ffc2f58..7ad7b83430e2 100644
--- 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
@@ -21,6 +21,7 @@ import org.apache.camel.tooling.maven.MavenGav;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 public class KnownDependenciesResolverTest {
@@ -53,4 +54,41 @@ public class KnownDependenciesResolverTest {
 
     public static class SomeClass {
     }
+
+    @Test
+    void theShippedMappingResolvesThirdPartyClassesByPackage() {
+        // CAMEL-24809: one line per library, matched by walking up the 
package; the Artemis package sits under the
+        // classic ActiveMQ one and must win for its own classes
+        KnownDependenciesResolver resolver = new KnownDependenciesResolver(new 
SimpleCamelContext(), null, null);
+        resolver.loadKnownDependencies();
+
+        assertGav(resolver, "org.postgresql.ds.PGSimpleDataSource", 
"org.postgresql", "postgresql");
+        assertGav(resolver, "org.postgresql.ds.PGConnectionPoolDataSource", 
"org.postgresql", "postgresql");
+        assertGav(resolver, "org.h2.jdbcx.JdbcDataSource", "com.h2database", 
"h2");
+        assertGav(resolver, "com.zaxxer.hikari.HikariConfig", "com.zaxxer", 
"HikariCP");
+        assertGav(resolver, 
"org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory", 
"org.apache.activemq",
+                "artemis-jakarta-client-all");
+        assertGav(resolver, "org.apache.activemq.ActiveMQConnectionFactory", 
"org.apache.activemq", "activemq-client");
+        assertGav(resolver, "org.apache.qpid.jms.JmsConnectionFactory", 
"org.apache.qpid", "qpid-jms-client");
+        assertGav(resolver, "com.fasterxml.jackson.databind.ObjectMapper", 
"com.fasterxml.jackson.core", "jackson-databind");
+        assertGav(resolver, "com.fasterxml.jackson.dataformat.xml.XmlMapper", 
"com.fasterxml.jackson.dataformat",
+                "jackson-dataformat-xml");
+        assertGav(resolver, "org.apache.commons.csv.CSVFormat", 
"org.apache.commons", "commons-csv");
+        assertGav(resolver, "software.amazon.awssdk.services.sqs.SqsClient", 
"software.amazon.awssdk", "sqs");
+        assertGav(resolver, "org.infinispan.client.hotrod.RemoteCacheManager", 
"org.infinispan", "infinispan-client-hotrod");
+        assertGav(resolver, "org.infinispan.manager.DefaultCacheManager", 
"org.infinispan", "infinispan-core");
+        assertGav(resolver, "freemarker.template.Configuration", 
"org.freemarker", "freemarker");
+        // a shared parent package is deliberately not mapped
+        assertEquals(null, 
resolver.mavenGavForClass("org.apache.commons.Anything"));
+    }
+
+    private static void assertGav(KnownDependenciesResolver resolver, String 
className, String groupId, String artifactId) {
+        MavenGav gav = resolver.mavenGavForClass(className);
+        assertNotNull(gav, className);
+        assertEquals(groupId, gav.getGroupId(), className);
+        assertEquals(artifactId, gav.getArtifactId(), className);
+        String version = gav.getVersion();
+        assertNotNull(version, className + " version is null");
+        assertFalse(version.startsWith("${"), className + " version is an 
unresolved placeholder: " + version);
+    }
 }
diff --git a/parent/pom.xml b/parent/pom.xml
index 3ca3b71227ac..8ef72df096f2 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -337,6 +337,7 @@
 
         <!-- NullAway / Error Prone (used by -Pnullcheck profile, requires JDK 
21+) -->
         <error-prone-version>2.50.0</error-prone-version>
+        <hikaricp-version>6.3.0</hikaricp-version>
         <nullaway-version>0.14.1</nullaway-version>
         <jt400-version>21.0.7</jt400-version>
         <jte-version>3.2.4</jte-version>
@@ -416,6 +417,8 @@
         <opa-version>2.1.1</opa-version>
         <opa-wasm-version>1.1.0</opa-wasm-version>
         <mcp-java-sdk-version>2.0.1</mcp-java-sdk-version>
+        <mssql-jdbc-version>12.10.0.jre11</mssql-jdbc-version>
+        <ojdbc-version>23.8.0.25.04</ojdbc-version>
         <openai-java-version>4.63.2</openai-java-version>
         <openapi-generator-version>7.25.0</openapi-generator-version>
         <openjpa-version>4.1.1</openjpa-version>
@@ -468,6 +471,7 @@
         <reactor-version>3.8.7</reactor-version>
         <reactor-netty-version>1.3.7</reactor-netty-version>
         <redisson-version>4.7.0</redisson-version>
+        <redshift-jdbc-version>2.1.0.33</redshift-jdbc-version>
         <resilience4j-version>2.4.0</resilience4j-version>
         <rest-assured-version>6.0.1</rest-assured-version>
         <roaster-version>2.31.1.Final</roaster-version>
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareKameletMainMojo.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareKameletMainMojo.java
index 19af7daae8a2..1180b0c23340 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareKameletMainMojo.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareKameletMainMojo.java
@@ -17,21 +17,36 @@
 package org.apache.camel.maven.packaging;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOError;
 import java.io.IOException;
 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.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
+import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
 
 import javax.inject.Inject;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 import org.apache.camel.tooling.model.ArtifactModel;
 import org.apache.camel.tooling.model.BaseModel;
@@ -45,6 +60,11 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.build.BuildContext;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
 
 import static org.apache.camel.tooling.util.PackageHelper.loadText;
 
@@ -77,12 +97,38 @@ public class PrepareKameletMainMojo extends AbstractMojo {
     @Parameter(defaultValue = "src/generated/")
     protected File genDir;
 
+    /**
+     * The third-party libraries camel run downloads on demand, mapped by 
package (CAMEL-24809): the input of
+     * camel-thirdparty-known-dependencies.properties.
+     */
+    @Parameter(defaultValue = 
"src/main/known-third-party-libraries.properties")
+    protected File thirdPartyLibraries;
+
+    /**
+     * Resolve every third-party jar and check that the mapped package is in 
it. Downloads the jars, so it is off by
+     * default and meant for CI: -Dcamel.known-dependencies.verify=true
+     */
+    @Parameter(defaultValue = "false", property = 
"camel.known-dependencies.verify")
+    protected boolean verifyThirdPartyJars;
+
+    private final RepositorySystem repoSystem;
+
+    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, 
required = true)
+    private RepositorySystemSession repoSession;
+
+    @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly 
= true, required = true)
+    private List<RemoteRepository> repositories;
+
+    private static final Pattern PROPERTY_VERSION = 
Pattern.compile("^\\$\\{([^}]+)\\}$");
+    private static final Pattern BOM_VERSION = 
Pattern.compile("^@bom\\(([^:]+):([^:]+):\\$\\{([^}]+)\\}\\)$");
+
     private final Map<Path, BaseModel<?>> allModels = new HashMap<>();
     private String licenseHeader;
 
     @Inject
-    public PrepareKameletMainMojo(BuildContext buildContext) {
+    public PrepareKameletMainMojo(BuildContext buildContext, RepositorySystem 
repoSystem) {
         this.buildContext = buildContext;
+        this.repoSystem = repoSystem;
     }
 
     /**
@@ -104,6 +150,180 @@ public class PrepareKameletMainMojo extends AbstractMojo {
         } catch (Exception e) {
             throw new MojoFailureException("Error updating 
camel-factoryfinder-known-dependencies.properties", e);
         }
+        try {
+            updateKnownThirdPartyDependencies();
+        } catch (MojoFailureException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new MojoFailureException("Error updating 
camel-thirdparty-known-dependencies.properties", e);
+        }
+    }
+
+    /**
+     * Generates camel-thirdparty-known-dependencies.properties from the 
curated list of third-party libraries
+     * (CAMEL-24809). Each input line maps a package to 
groupId:artifactId:version, where the version is a ${property}
+     * of the project (inherited from camel-parent) or 
@bom(groupId:artifactId:${property}) for a library whose version
+     * a BOM manages. The version is resolved here, so the runtime needs no 
lookup, and a property that does not exist
+     * or a literal version fails the build. With verifyThirdPartyJars every 
jar is resolved and the mapped package must
+     * be found in it.
+     */
+    protected void updateKnownThirdPartyDependencies() throws Exception {
+        File input = thirdPartyLibraries.isAbsolute()
+                ? thirdPartyLibraries : new File(project.getBasedir(), 
thirdPartyLibraries.getPath());
+        if (!input.exists()) {
+            getLog().info("No " + input + ": 
camel-thirdparty-known-dependencies.properties not generated");
+            return;
+        }
+        Properties in = new Properties();
+        try (InputStream is = new FileInputStream(input)) {
+            in.load(is);
+        }
+        Map<String, String> boms = new LinkedHashMap<>();
+        List<String> problems = new ArrayList<>();
+        Map<String, String> resolved = new TreeMap<>();
+        for (String pkg : in.stringPropertyNames()) {
+            String gav = in.getProperty(pkg).trim();
+            int i = gav.indexOf(':');
+            int j = gav.indexOf(':', i + 1);
+            if (i < 0 || j < 0) {
+                problems.add(pkg + " = " + gav + ": expected 
groupId:artifactId:version");
+                continue;
+            }
+            String groupId = gav.substring(0, i);
+            String artifactId = gav.substring(i + 1, j);
+            String version = gav.substring(j + 1);
+            Matcher pm = PROPERTY_VERSION.matcher(version);
+            Matcher bm = BOM_VERSION.matcher(version);
+            if (pm.matches()) {
+                String value = 
project.getProperties().getProperty(pm.group(1));
+                if (value == null) {
+                    problems.add(pkg + ": no property " + pm.group(1) + " in 
parent/pom.xml");
+                    continue;
+                }
+                version = value;
+            } else if (bm.matches()) {
+                String bomVersion = 
project.getProperties().getProperty(bm.group(3));
+                if (bomVersion == null) {
+                    problems.add(pkg + ": no property " + bm.group(3) + " in 
parent/pom.xml");
+                    continue;
+                }
+                String bomKey = bm.group(1) + ":" + bm.group(2) + ":" + 
bomVersion;
+                version = managedVersion(boms, bomKey, groupId, artifactId);
+                if (version == null) {
+                    problems.add(pkg + ": " + groupId + ":" + artifactId + " 
is not managed by " + bomKey);
+                    continue;
+                }
+            } else {
+                problems.add(pkg + " = " + gav + ": a literal version; add a 
<" + artifactId.toLowerCase(Locale.ROOT)
+                             + "-version> property to parent/pom.xml and use 
${...}");
+                continue;
+            }
+            resolved.put(pkg, groupId + ":" + artifactId + ":" + version);
+        }
+        if (!problems.isEmpty()) {
+            throw new MojoFailureException("Problems in " + input + ":\n  " + 
String.join("\n  ", problems));
+        }
+        if (verifyThirdPartyJars) {
+            verifyPackagesInJars(resolved);
+        }
+        List<String> lines = new ArrayList<>();
+        lines.add("# Generated by 
camel-package-maven-plugin:prepare-kamelet-main from src/main/"
+                  + input.getName() + " (CAMEL-24809). Do not edit.");
+        lines.add("# Third-party libraries camel run downloads on demand, 
mapped by package; the resolver matches the class"
+                  + " and then each enclosing package.");
+        for (Map.Entry<String, String> e : resolved.entrySet()) {
+            lines.add(e.getKey() + " = " + e.getValue());
+        }
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream("license-header.txt")) {
+            this.licenseHeader = loadText(is);
+        } catch (Exception e) {
+            throw new MojoFailureException("Error loading license-header.txt 
file", e);
+        }
+        // into target/classes, not src/generated: the file is the input with 
the versions resolved, so keeping both
+        // in the repository would duplicate 146 lines; it is regenerated on 
every build and shipped in the jar
+        Path out = Path.of(project.getBuild().getOutputDirectory(), 
"camel-thirdparty-known-dependencies.properties");
+        Files.createDirectories(out.getParent());
+        updateResource(buildContext, out, licenseHeader + "\n" + 
String.join("\n", lines) + "\n");
+        getLog().info("Generated " + out.getFileName() + " with " + 
resolved.size() + " libraries");
+    }
+
+    private String managedVersion(Map<String, String> cache, String bomKey, 
String groupId, String artifactId)
+            throws Exception {
+        String key = bomKey + "->" + groupId + ":" + artifactId;
+        if (cache.containsKey(key)) {
+            return cache.get(key);
+        }
+        String[] parts = bomKey.split(":");
+        ArtifactRequest req = new 
ArtifactRequest().setRepositories(repositories)
+                .setArtifact(new DefaultArtifact(parts[0], parts[1], "pom", 
parts[2]));
+        File pom = repoSystem.resolveArtifact(repoSession, 
req).getArtifact().getFile();
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+        Document doc = dbf.newDocumentBuilder().parse(pom);
+        String found = null;
+        NodeList deps = doc.getElementsByTagName("dependency");
+        for (int n = 0; n < deps.getLength() && found == null; n++) {
+            Element d = (Element) deps.item(n);
+            if (groupId.equals(text(d, "groupId")) && 
artifactId.equals(text(d, "artifactId"))) {
+                found = text(d, "version");
+            }
+        }
+        if (found != null && found.startsWith("${") && found.endsWith("}")) {
+            // a BOM that versions its artifacts through its own properties
+            String prop = found.substring(2, found.length() - 1);
+            String value = "project.version".equals(prop) ? parts[2] : null;
+            NodeList props = doc.getElementsByTagName("properties");
+            for (int n = 0; n < props.getLength() && value == null; n++) {
+                value = text((Element) props.item(n), prop);
+            }
+            found = value;
+        }
+        cache.put(key, found);
+        return found;
+    }
+
+    private static String text(Element parent, String child) {
+        for (Node n = parent.getFirstChild(); n != null; n = 
n.getNextSibling()) {
+            if (n.getNodeType() == Node.ELEMENT_NODE && 
child.equals(n.getNodeName())) {
+                return n.getTextContent().trim();
+            }
+        }
+        return null;
+    }
+
+    private void verifyPackagesInJars(Map<String, String> resolved) throws 
Exception {
+        List<String> problems = new ArrayList<>();
+        Map<String, File> jars = new LinkedHashMap<>();
+        for (Map.Entry<String, String> e : resolved.entrySet()) {
+            String gav = e.getValue();
+            File jar = jars.get(gav);
+            if (jar == null) {
+                String[] parts = gav.split(":");
+                ArtifactRequest req = new 
ArtifactRequest().setRepositories(repositories)
+                        .setArtifact(new DefaultArtifact(parts[0], parts[1], 
"jar", parts[2]));
+                try {
+                    jar = repoSystem.resolveArtifact(repoSession, 
req).getArtifact().getFile();
+                } catch (Exception ex) {
+                    problems.add(e.getKey() + ": cannot resolve " + gav + ": " 
+ ex.getMessage());
+                    continue;
+                }
+                jars.put(gav, jar);
+            }
+            String dir = e.getKey().replace('.', '/') + "/";
+            boolean found;
+            try (ZipFile zip = new ZipFile(jar)) {
+                found = zip.stream().anyMatch(z -> z.getName().startsWith(dir) 
&& z.getName().endsWith(".class"));
+            }
+            if (!found) {
+                problems.add(e.getKey() + ": no classes under " + dir + " in " 
+ gav);
+            }
+        }
+        if (!problems.isEmpty()) {
+            throw new MojoFailureException(
+                    "Third-party known dependencies do not match their jars:\n 
 "
+                                           + String.join("\n  ", problems));
+        }
+        getLog().info("Verified " + resolved.size() + " third-party packages 
against " + jars.size() + " jars");
     }
 
     protected void updateKnownDependencies() throws Exception {

Reply via email to