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

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

commit c416c1abfdab09e0a989ac8ac64636791efb8420
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 10:04:19 2026 +0200

    chore: camel-jbang - the bean class check honours the known dependencies 
camel run downloads, and says how to declare an unknown one
    
    A bean declared in YAML with a class that is not on the CLI classpath, such 
as
    #class:org.postgresql.ds.PGSimpleDataSource or the Artemis 
ActiveMQConnectionFactory, was
    reported by camel validate and the write tool as "class ... was not found", 
while camel run
    resolves exactly these classes through 
camel-main-known-dependencies.properties, downloads
    the jar and starts the route. The validator contradicted the runtime.
    
    The check now loads the same two mapping files camel-kamelet-main uses
    (camel-main-known-dependencies.properties, 
camel-component-known-dependencies.properties)
    and matches the class name and then each enclosing package, as
    KnownDependenciesResolver.findGav does; a mapped class is not an error. For 
a class in no
    mapping the message says how to declare the dependency: 
camel.jbang.dependencies in
    application.properties or --dep on camel run.
    
    Two tests: the Postgres datasource and the Artemis connection factory pass 
without a
    dependency declared; a class from an unknown library gets the declaration 
hint.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../dsl/jbang/core/commands/ai/BeanRefChecks.java  | 54 +++++++++++++++++++++-
 .../commands/ai/SourceValidatorBeanRefsTest.java   | 37 +++++++++++++++
 2 files changed, 89 insertions(+), 2 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/BeanRefChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/BeanRefChecks.java
index ad8d2ce43ac6..06c9a0efc14e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/BeanRefChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/BeanRefChecks.java
@@ -16,14 +16,19 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.ai;
 
+import java.io.InputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.regex.Matcher;
@@ -340,6 +345,46 @@ final class BeanRefChecks {
      * A class named with its package that is neither next to the route nor on 
the CLI classpath: the wrong package
      * (org.apache.camel.support.StringAggregationStrategy) or a missing 
dependency. Null when the class is fine.
      */
+    /**
+     * The classes camel run resolves to a Maven dependency and downloads on 
demand (camel-kamelet-main's
+     * camel-main-known-dependencies.properties and 
camel-component-known-dependencies.properties), so a
+     * #class:org.postgresql.ds.PGSimpleDataSource bean is fine without a 
dependency declared even though the class is
+     * not on the CLI classpath. Matched the way the runtime matches: the 
class name, then each enclosing package.
+     */
+    private static volatile Map<String, String> knownDependencies;
+
+    static String knownDependency(String fqcn) {
+        Map<String, String> known = knownDependencies;
+        if (known == null) {
+            known = new HashMap<>();
+            for (String name : new String[] {
+                    "camel-main-known-dependencies.properties", 
"camel-component-known-dependencies.properties" }) {
+                try {
+                    Enumeration<URL> resources = 
BeanRefChecks.class.getClassLoader().getResources(name);
+                    while (resources.hasMoreElements()) {
+                        try (InputStream is = 
resources.nextElement().openStream()) {
+                            Properties prop = new Properties();
+                            prop.load(is);
+                            for (String key : prop.stringPropertyNames()) {
+                                known.put(key, prop.getProperty(key));
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    // the mapping is an optimisation of the message, not a 
requirement
+                }
+            }
+            knownDependencies = known;
+        }
+        String prefix = fqcn;
+        String gav = known.get(prefix);
+        while (gav == null && prefix.lastIndexOf('.') != -1) {
+            prefix = prefix.substring(0, prefix.lastIndexOf('.'));
+            gav = known.get(prefix);
+        }
+        return gav;
+    }
+
     static String classNotFound(String fqcn, BeanDeclarations external) {
         if (external == null || external == BeanDeclarations.NONE
                 || fqcn == null || fqcn.contains("{{") || fqcn.contains("${")) 
{
@@ -366,6 +411,10 @@ final class BeanRefChecks {
         } catch (Throwable e) {
             // not on the classpath
         }
+        if (knownDependency(fqcn) != null) {
+            // camel run downloads the dependency for this class; the runtime 
and the validator must agree
+            return null;
+        }
         String hint = "";
         if (simple.endsWith("AggregationStrategy") && 
!fqcn.startsWith("org.apache.camel.processor.aggregate.")) {
             String candidate = "org.apache.camel.processor.aggregate." + 
simple;
@@ -377,8 +426,9 @@ final class BeanRefChecks {
             }
         }
         if (hint.isEmpty()) {
-            hint = " (check the package name; a class of your own goes in a 
.java file next to the route, a class from"
-                   + " another library needs its dependency)";
+            hint = " (check the package name; a class of your own goes in a 
.java file next to the route; a class from"
+                   + " another library needs its dependency declared, 
camel.jbang.dependencies=<groupId>:<artifactId>:<version>"
+                   + " in application.properties or --dep on camel run)";
         }
         return "class " + fqcn + " was not found" + hint;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
index cb468d430b02..b2903c9a754e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
@@ -350,6 +350,43 @@ public class SourceValidatorBeanRefsTest {
                 .contains("did you mean 
org.apache.camel.processor.aggregate.StringAggregationStrategy?");
     }
 
+    @Test
+    void aClassCamelRunDownloadsIsNotReportedAsMissing(@TempDir Path dir) 
throws IOException {
+        // the Postgres datasource and the Artemis connection factory are not 
on the CLI classpath, but camel run
+        // resolves them to their Maven dependency 
(camel-main-known-dependencies.properties) and downloads it, so a
+        // bean of that type runs; the validator must not contradict the 
runtime
+        List<String> msgs = SourceValidator.validate("r.camel.yaml", """
+                - beans:
+                    - name: postgresDS
+                      type: "#class:org.postgresql.ds.PGSimpleDataSource"
+                      properties:
+                        url: "jdbc:postgresql://localhost:5432/postgres"
+                    - name: artemisCF
+                      type: 
"#class:org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory"
+                - route:
+                    from:
+                      uri: "timer:tick?period=1000"
+                      steps:
+                        - to:
+                            uri: "sql:select 1?dataSource=#postgresDS"
+                """, CATALOG, null, dir);
+        assertThat(msgs).isEmpty();
+        
assertThat(BeanRefChecks.knownDependency("org.postgresql.ds.PGSimpleDataSource"))
+                .startsWith("org.postgresql:postgresql");
+        
assertThat(BeanRefChecks.knownDependency("com.example.NoSuchThing")).isNull();
+    }
+
+    @Test
+    void aClassFromAnUnknownLibrarySaysHowToDeclareTheDependency(@TempDir Path 
dir) throws IOException {
+        List<String> msgs = SourceValidator.validate("r.camel.yaml", """
+                - beans:
+                    - name: pool
+                      type: "#class:com.zaxxer.hikari.HikariDataSourceX"
+                """, CATALOG, null, dir);
+        assertThat(msgs).hasSize(1);
+        assertThat(msgs.get(0)).contains("was not 
found").contains("camel.jbang.dependencies=<groupId>:<artifactId>:<version>");
+    }
+
     @Test
     void aSiblingClassImportedFromTheWrongPackageIsNamed(@TempDir Path dir) 
throws IOException {
         Files.writeString(dir.resolve("MemoryLeakSimulator.java"), """

Reply via email to