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 15b23e748001 CAMEL-24852: camel run looks a missing classpath: or 
file: resource up next to the route files; the validator reports a resource 
that is nowhere and a dynamic file directory
15b23e748001 is described below

commit 15b23e748001cad95d26ccfa58e3f6ccf0c0fbeb
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 20 21:01:10 2026 +0200

    CAMEL-24852: camel run looks a missing classpath: or file: resource up next 
to the route files; the validator reports a resource that is nowhere and a 
dynamic file directory
    
    Two startup failures from the round-2 benchmark where the validator was 
clean
    and the runtime refused the file.
    
    resource:classpath:shipment-mapping.groovy with the script next to the route
    failed in camel run: a .groovy file is a source the CLI loads itself and 
does
    not put on the classpath (an .xsl or .json next to it is), while
    resource:file: worked in the CLI but not after camel export. The
    DependencyDownloaderResourceLoader, which already looked a missing
    classpath:/file: resource up under --source-dir, now does the same without
    one in the directories of the route files (the working directory first, then
    the directories of camel.main.routesIncludePattern), so the classpath: form
    works in both places. A resource that exists nowhere fails as before, named
    as written; the validator reports it before the run, with the name-only form
    when the file is elsewhere in the directory.
    
    The directory of a file endpoint cannot be dynamic, and the runtime said so
    only at startup: the endpoint checks now report a ${} placeholder in the
    directory on to: and from:, with the form to write (a fixed directory with
    fileName, or toD:). toD, wireTap, enrich and pollEnrich evaluate the
    expression first and are left alone.
    
    Closes #26634
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  6 ++
 .../dsl/jbang/core/commands/ai/BeanRefChecks.java  | 38 ++++++++++
 .../dsl/jbang/core/commands/ai/EndpointChecks.java | 35 +++++++++
 .../commands/ai/SourceValidatorEndpointTest.java   | 32 +++++++++
 .../ai/SourceValidatorResourceRefsTest.java        | 83 ++++++++++++++++++++++
 .../java/org/apache/camel/main/KameletMain.java    | 26 ++++++-
 .../DependencyDownloaderResourceLoader.java        | 45 ++++++++----
 .../DependencyDownloaderResourceLoaderTest.java    | 76 ++++++++++++++++++++
 8 files changed, 328 insertions(+), 13 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index e7d5ca92879a..2f627f9065a9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -801,6 +801,12 @@ or `resource:file:mapping.groovy`, a mapping script used 
as the body of an expre
 the reference resolves in the exported project. Other Groovy files go to 
`src/main/resources/camel-groovy` as before,
 where they are compiled as scripts at startup.
 
+`camel run` now looks up a `classpath:` or `file:` resource that is not found 
in the directories of the route
+files (the working directory first), the way it already did under 
`--source-dir`. A script next to the route is
+found as `resource:classpath:mapping.groovy`, the reference that also works in 
the project `camel export` writes,
+where before only `resource:file:mapping.groovy` worked in the CLI. A resource 
that exists nowhere fails as before,
+named as written.
+
 === camel-jbang (MCP servers)
 
 The Camel authoring tools for AI agents are now defined once, in 
`camel-jbang-core`, and exposed under the
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 f8803cf9c69f..5ea3ef07ece2 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
@@ -553,7 +553,45 @@ final class BeanRefChecks {
             errors.add("Line " + (i + 1) + ": " + scheme + ": the file " + 
path + " does not exist in the directory"
                        + hint);
         }
+        validateExpressionResourceRefs(lines, directory, errors);
         return errors;
     }
 
+    /** resource:classpath:x or resource:file:x as the value of an expression 
(groovy, xslt, ...) or an option. */
+    private static final Pattern RESOURCE_REF_PATTERN = 
Pattern.compile("resource:(classpath|file):([^\"'\\s?&,]+)");
+
+    /**
+     * A resource:classpath:x or resource:file:x in an expression whose file 
is not in the directory fails when the
+     * route starts (camel run looks a resource up next to the route files, 
CAMEL-24852). Says what is missing and, when
+     * a file of that name is elsewhere in the directory, the reference to 
write.
+     */
+    static void validateExpressionResourceRefs(String[] lines, Path directory, 
List<String> errors) {
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            if (line.trim().startsWith("#")) {
+                continue;
+            }
+            Matcher m = RESOURCE_REF_PATTERN.matcher(line);
+            while (m.find()) {
+                String scheme = m.group(1);
+                String path = m.group(2);
+                if (path.startsWith("//")) {
+                    path = path.substring(2);
+                }
+                if (path.startsWith("{{") || path.contains("${")) {
+                    continue; // a placeholder
+                }
+                String base = path.substring(path.lastIndexOf('/') + 1);
+                boolean exists = Files.exists(directory.resolve(path));
+                if (!exists && !path.startsWith("/")) {
+                    String hint = !base.isEmpty() && !base.equals(path) && 
Files.exists(directory.resolve(base))
+                            ? " (the directory has " + base + ": write 
resource:" + scheme + ":" + base + ")"
+                            : " (add the file next to the route files)";
+                    errors.add("Line " + (i + 1) + ": resource:" + scheme + 
":" + path
+                               + ": the file does not exist in the directory" 
+ hint);
+                }
+            }
+        }
+    }
+
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
index 4f78eb90a80b..2c3c0977362b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
@@ -233,6 +233,7 @@ final class EndpointChecks {
                     collectEndpointErrors(errors, result, scheme, i, 
optionLineMap);
                 }
                 checkRegexOptions(errors, fullUri, i, optionLineMap);
+                checkDynamicDirectory(errors, fullUri, i, eipName);
             } catch (Exception e) {
                 // ignore validation errors
             }
@@ -337,6 +338,40 @@ final class EndpointChecks {
         }
     }
 
+    /**
+     * file:archived/${header.monthDir} on a to: fails at startup: the 
directory of a file endpoint cannot be dynamic
+     * (the runtime says "Dynamic expressions with ${ } placeholders is not 
allowed. Use the fileName option"). Says to
+     * keep the directory fixed and put the dynamic part in fileName, or to 
use toD: (which evaluates the uri first).
+     * toD, wireTap, enrich and pollEnrich evaluate the expression before the 
endpoint is created and are left alone.
+     */
+    static void checkDynamicDirectory(List<String> errors, String fullUri, int 
uriLineIdx, String eipName) {
+        int colon = fullUri.indexOf(':');
+        if (colon < 0 || !FILE_SCHEMES.contains(fullUri.substring(0, colon))) {
+            return;
+        }
+        if (eipName == null || !eipName.equals("to") && 
!eipName.equals("from")) {
+            // an unresolved parent may be a toD: or wireTap:, which evaluate 
the uri first: leave it alone
+            return;
+        }
+        int q = fullUri.indexOf('?');
+        String dir = q >= 0 ? fullUri.substring(colon + 1, q) : 
fullUri.substring(colon + 1);
+        if (dir.startsWith("//")) {
+            dir = dir.substring(2);
+        }
+        if (!dir.contains("${")) {
+            return;
+        }
+        String scheme = fullUri.substring(0, colon);
+        String fixed = dir.substring(0, dir.indexOf("${"));
+        if (fixed.endsWith("/")) {
+            fixed = fixed.substring(0, fixed.length() - 1);
+        }
+        errors.add(linePrefix(uriLineIdx) + scheme + ": the directory " + dir 
+ " cannot be dynamic (the runtime"
+                   + " says 'Dynamic expressions with ${ } placeholders is not 
allowed. Use the fileName option'):"
+                   + " keep the directory fixed and put the dynamic part in 
fileName (" + scheme + ":" + fixed
+                   + "?fileName=${...}), or use toD: with the whole uri, which 
evaluates it per message");
+    }
+
     /** A wildcard such as *.txt as the regex .*\\.txt. */
     static String toRegex(String wildcard) {
         StringBuilder sb = new StringBuilder();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
index 9c4d44acc4e3..767162c3e192 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
@@ -560,4 +560,36 @@ class SourceValidatorEndpointTest {
         assertThat(msgs).hasSize(1);
         assertThat(msgs.get(0)).contains("mock is a producer-only component: 
it cannot be a from:").contains("direct:name");
     }
+
+    /** CAMEL-24852: the directory of a file endpoint on a to: cannot be 
dynamic; toD: evaluates the uri first. */
+    @Test
+    void aDynamicDirectoryOnAFileEndpointSaysToUseFileNameOrToD() {
+        String yaml = """
+                - route:
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to:
+                            uri: 
"file://archived/${header.monthDir}?fileName=${header.CamelFileName}"
+                """;
+        List<String> errors = SourceValidator.validateYamlEndpoints(yaml, 
catalog);
+        assertThat(errors)
+                .anyMatch(e -> e.startsWith("Line 6: file: the directory 
archived/${header.monthDir} cannot be dynamic")
+                        && e.contains("fileName 
(file:archived?fileName=${...})") && e.contains("use toD:"));
+
+        List<String> dynamic = 
SourceValidator.validateYamlEndpoints(yaml.replace("- to:", "- toD:"), catalog);
+        assertThat(dynamic).noneMatch(e -> e.contains("cannot be dynamic"));
+
+        // a from: with a dynamic directory fails at startup the same way
+        String fromYaml = """
+                - route:
+                    from:
+                      uri: "file://archived/${header.monthDir}"
+                      steps:
+                        - to:
+                            uri: log:done
+                """;
+        assertThat(SourceValidator.validateYamlEndpoints(fromYaml, catalog))
+                .anyMatch(e -> e.startsWith("Line 3: file: the directory 
archived/${header.monthDir} cannot be dynamic"));
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorResourceRefsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorResourceRefsTest.java
new file mode 100644
index 000000000000..d9bdc5c972c3
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorResourceRefsTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ai;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-24852: a resource:classpath: or resource:file: reference in an 
expression, checked against the files next to
+ * the route, where camel run looks them up.
+ */
+class SourceValidatorResourceRefsTest {
+
+    @TempDir
+    Path dir;
+
+    private static final String ROUTE = """
+            - route:
+                from:
+                  uri: timer:tick
+                  steps:
+                    - setBody:
+                        expression:
+                          groovy:
+                            expression: "resource:%s"
+                    - to:
+                        uri: log:done
+            """;
+
+    @Test
+    void aClasspathReferenceToAScriptNextToTheRouteIsFine() throws Exception {
+        // camel run looks a classpath: resource up next to the route files 
(DependencyDownloaderResourceLoader)
+        Files.writeString(dir.resolve("shipment-mapping.groovy"), "body");
+        
assertThat(SourceValidator.validateResourceRefs(ROUTE.formatted("classpath:shipment-mapping.groovy"),
 dir)).isEmpty();
+    }
+
+    @Test
+    void aFileReferenceToAScriptNextToTheRouteIsFine() throws Exception {
+        Files.writeString(dir.resolve("shipment-mapping.groovy"), "body");
+        
assertThat(SourceValidator.validateResourceRefs(ROUTE.formatted("file:shipment-mapping.groovy"),
 dir)).isEmpty();
+    }
+
+    @Test
+    void aClasspathReferenceToAStylesheetNextToTheRouteIsFine() throws 
Exception {
+        // an .xsl is not a source: camel run puts it on the classpath
+        Files.writeString(dir.resolve("packing-slip.xsl"), "<xsl/>");
+        
assertThat(SourceValidator.validateResourceRefs(ROUTE.formatted("classpath:packing-slip.xsl"),
 dir)).isEmpty();
+    }
+
+    @Test
+    void aReferenceToAFileThatIsNotThereSaysSo() throws Exception {
+        List<String> errors = 
SourceValidator.validateResourceRefs(ROUTE.formatted("file:mapping/shipment.groovy"),
 dir);
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0))
+                .startsWith("Line 8: resource:file:mapping/shipment.groovy: 
the file does not exist in the directory")
+                .contains("add the file next to the route files");
+
+        Files.writeString(dir.resolve("shipment.groovy"), "body");
+        errors = 
SourceValidator.validateResourceRefs(ROUTE.formatted("file:mapping/shipment.groovy"),
 dir);
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0)).contains("the directory has shipment.groovy: 
write resource:file:shipment.groovy");
+    }
+}
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 419f2ae9fe1a..0da255df653a 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -116,6 +116,7 @@ import org.apache.camel.support.RouteOnDemandReloadStrategy;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.support.startup.BacklogStartupStepRecorder;
 import org.apache.camel.tooling.maven.MavenGav;
+import org.apache.camel.util.FileUtil;
 
 /**
  * A Main class for booting up Camel with Kamelet in standalone mode.
@@ -715,7 +716,7 @@ public class KameletMain extends MainCommandLineSupport {
             
answer.getCamelContextExtension().addContextPlugin(UriFactoryResolver.class,
                     new DependencyDownloaderUriFactoryResolver(answer));
             
answer.getCamelContextExtension().addContextPlugin(ResourceLoader.class,
-                    new DependencyDownloaderResourceLoader(answer, sourceDir));
+                    new DependencyDownloaderResourceLoader(answer, sourceDir, 
routeDirectories()));
             
answer.getCamelContextExtension().addContextPlugin(OptimisedComponentResolver.class,
                     new KameletOptimisedComponentResolver(answer));
 
@@ -868,6 +869,29 @@ public class KameletMain extends MainCommandLineSupport {
         return new KameletAutowiredLifecycleStrategy(camelContext, 
stubPattern, silent);
     }
 
+    /**
+     * The directories of the route files (from 
camel.main.routesIncludePattern), the working directory first: where a
+     * classpath: or file: resource that is not found is looked up, so a 
script next to the route is found by name
+     * (CAMEL-24852).
+     */
+    List<String> routeDirectories() {
+        List<String> dirs = new ArrayList<>();
+        dirs.add(".");
+        String routes = 
getInitialProperties().getProperty("camel.main.routesIncludePattern");
+        if (routes != null) {
+            for (String route : routes.split(",")) {
+                route = route.trim();
+                if (route.startsWith("file:")) {
+                    String dir = FileUtil.onlyPath(route.substring(5));
+                    if (dir != null && !dir.isEmpty() && !dirs.contains(dir)) {
+                        dirs.add(dir);
+                    }
+                }
+            }
+        }
+        return dirs;
+    }
+
     protected ClassLoader createApplicationContextClassLoader(CamelContext 
camelContext) {
         if (classLoader == null) {
             // jars need to be added to dependency downloader classloader
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/DependencyDownloaderResourceLoader.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/DependencyDownloaderResourceLoader.java
index 280384a383ad..ee29cb7dfe06 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/DependencyDownloaderResourceLoader.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/DependencyDownloaderResourceLoader.java
@@ -17,6 +17,7 @@
 package org.apache.camel.main.download;
 
 import java.io.File;
+import java.util.List;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.impl.engine.DefaultResourceLoader;
@@ -27,10 +28,22 @@ public class DependencyDownloaderResourceLoader extends 
DefaultResourceLoader {
 
     private final DependencyDownloader downloader;
     private final String sourceDir;
+    private final List<String> fallbackDirs;
 
     public DependencyDownloaderResourceLoader(CamelContext camelContext, 
String sourceDir) {
+        this(camelContext, sourceDir, List.of());
+    }
+
+    /**
+     * @param sourceDir    the --source-dir, when used: a classpath: or file: 
resource not found is looked up there
+     * @param fallbackDirs the directories of the route files (CAMEL-24852): 
without a source-dir, a classpath: or file:
+     *                     resource not found is looked up in them, so 
resource:classpath:mapping.groovy finds the
+     *                     script next to the route the way it does in an 
exported project
+     */
+    public DependencyDownloaderResourceLoader(CamelContext camelContext, 
String sourceDir, List<String> fallbackDirs) {
         super(camelContext);
         this.sourceDir = sourceDir;
+        this.fallbackDirs = fallbackDirs != null ? fallbackDirs : List.of();
         this.downloader = camelContext.hasService(DependencyDownloader.class);
     }
 
@@ -50,19 +63,27 @@ public class DependencyDownloaderResourceLoader extends 
DefaultResourceLoader {
             }
         }
         Resource answer = super.resolveResource(uri);
-        if (sourceDir != null) {
-            boolean exists = answer != null && answer.exists();
-            // if not found then we need to look again inside the source-dir 
which we can do
-            // for file and classpath resources
-            if (!exists && ("classpath".equals(scheme) || 
"file".equals(scheme))) {
-                String path = StringHelper.after(uri, ":");
-                // strip leading double slash
-                if (path.startsWith("//")) {
-                    path = path.substring(2);
+        boolean exists = answer != null && answer.exists();
+        if (!exists && ("classpath".equals(scheme) || "file".equals(scheme))) {
+            String path = StringHelper.after(uri, ":");
+            // strip leading double slash
+            if (path.startsWith("//")) {
+                path = path.substring(2);
+            }
+            if (sourceDir != null) {
+                // if not found then we need to look again inside the 
source-dir which we can do
+                // for file and classpath resources: force to load from file 
system when using source-dir
+                answer = super.resolveResource("file:" + sourceDir + 
File.separator + path);
+            } else {
+                // the files next to the routes: the first directory that has 
it wins, else the original answer
+                // (so the error names the resource as written)
+                for (String dir : fallbackDirs) {
+                    Resource candidate = super.resolveResource("file:" + dir + 
File.separator + path);
+                    if (candidate != null && candidate.exists()) {
+                        answer = candidate;
+                        break;
+                    }
                 }
-                // force to load from file system when using source-dir
-                uri = "file" + ":" + sourceDir + File.separator + path;
-                answer = super.resolveResource(uri);
             }
         }
         return answer;
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/DependencyDownloaderResourceLoaderTest.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/DependencyDownloaderResourceLoaderTest.java
new file mode 100644
index 000000000000..810dd3d32dab
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/DependencyDownloaderResourceLoaderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.main.download;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.impl.engine.SimpleCamelContext;
+import org.apache.camel.spi.Resource;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** CAMEL-24852: a classpath: or file: resource not found is looked up in the 
directories of the route files. */
+public class DependencyDownloaderResourceLoaderTest {
+
+    @TempDir
+    Path routes;
+
+    @Test
+    void aScriptNextToTheRouteIsFoundByClasspathName() throws Exception {
+        Files.writeString(routes.resolve("mapping.groovy"), "body");
+        SimpleCamelContext context = new SimpleCamelContext();
+        DependencyDownloaderResourceLoader loader
+                = new DependencyDownloaderResourceLoader(context, null, 
List.of(routes.toString()));
+
+        Resource resource = loader.resolveResource("classpath:mapping.groovy");
+        assertTrue(resource.exists());
+        assertEquals("body", new 
String(resource.getInputStream().readAllBytes()));
+
+        resource = loader.resolveResource("file:mapping.groovy");
+        assertTrue(resource.exists(), "a file: reference relative to the route 
directory");
+    }
+
+    @Test
+    void aResourceThatIsNowhereKeepsItsName() {
+        SimpleCamelContext context = new SimpleCamelContext();
+        DependencyDownloaderResourceLoader loader
+                = new DependencyDownloaderResourceLoader(context, null, 
List.of(routes.toString()));
+
+        Resource resource = loader.resolveResource("classpath:missing.groovy");
+        assertFalse(resource.exists());
+        assertEquals("classpath:missing.groovy", resource.getLocation(), "the 
error names the resource as written");
+    }
+
+    @Test
+    void theSourceDirWinsWhenSet() throws Exception {
+        Path sourceDir = Files.createDirectory(routes.resolve("src"));
+        Files.writeString(sourceDir.resolve("mapping.groovy"), "from source 
dir");
+        Files.writeString(routes.resolve("mapping.groovy"), "next to the 
route");
+        SimpleCamelContext context = new SimpleCamelContext();
+        DependencyDownloaderResourceLoader loader
+                = new DependencyDownloaderResourceLoader(context, 
sourceDir.toString(), List.of(routes.toString()));
+
+        Resource resource = loader.resolveResource("classpath:mapping.groovy");
+        assertEquals("from source dir", new 
String(resource.getInputStream().readAllBytes()));
+    }
+}

Reply via email to