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"), """
