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()));
+ }
+}