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 9f17e36f87b2 CAMEL-25865: camel-jbang - the tools and the Simple docs
say how to read a data file (#28144)
9f17e36f87b2 is described below
commit 9f17e36f87b236800f5f39edab47b05ce76b55f3
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Oct 20 12:34:47 2026 +0200
CAMEL-25865: camel-jbang - the tools and the Simple docs say how to read a
data file (#28144)
* CAMEL-25865: camel-jbang - the tools and the Simple docs say how to read
a data file: constant with resource:file:, and load() under camel run
For the file component, camel_catalog_doc and the hint for a consumer
option on a to: pointed only to the poll EIP
and pollEnrich, which consume the file. They now add that a data file the
route needs, such as a lookup table, is
read with the constant language and resource:file:. The Simple docs say
that camel run puts the files of the project
on the classpath, so load() reads them there, and point to resource:file:
for the file system in any runtime.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25865: simple - load() says it reads the classpath only when a name
with file: is not found there
The load function drops the prefix and looks on the classpath, on purpose
(CAMEL-22955), so
${load(file:/var/inbox/data.json)} failed with only "Cannot find resource
... in classpath". The error now says that the
prefix is not used, and for file: that the constant language with
resource:file: reads a file of the file-system.
A name that is on the classpath still loads, with or without the prefix.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../camel/catalog/docs/simple-functions.adoc | 8 +++++++-
.../modules/languages/pages/simple-functions.adoc | 8 +++++++-
.../language/simple/MiscExpressionBuilder.java | 24 ++++++++++++++++++++++
.../apache/camel/language/simple/SimpleTest.java | 16 +++++++++++++++
.../dsl/jbang/core/commands/ai/CatalogDocs.java | 10 +++++++--
.../dsl/jbang/core/commands/ai/EndpointChecks.java | 7 ++++++-
.../commands/ai/SourceValidatorEndpointTest.java | 21 +++++++++++++++++--
7 files changed, 87 insertions(+), 7 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
index eb35a1bb283f..88213e28a724 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
@@ -500,7 +500,13 @@ If the file is not present on classpath, then an exception
is thrown. However, y
IMPORTANT: The load function can only load from classpath; and not the
file-system in general. This is on purpose
to avoid potential security risks by constructing dynamic simple expressions
using the load function to attempt
-to load server files.
+to load server files. A prefix such as `file:` is not used:
`$\{load(file:/var/inbox/data.json)}` looks for
+`/var/inbox/data.json` on the classpath, and when it is not there, the error
says so.
+
+With Camel JBang, `camel run` puts the files of the project on the classpath,
such as a `stock.json` next to the
+routes, so `$\{load(stock.json)}` reads it there. To read a file of the
file-system in any runtime, use the
+xref:constant-language.adoc[Constant] language with `resource:file:`, such as
`resource:file:stock.json`, for example
+in a `setVariable` that keeps the content for the steps after it.
=== String Functions
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
index eb35a1bb283f..88213e28a724 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc
@@ -500,7 +500,13 @@ If the file is not present on classpath, then an exception
is thrown. However, y
IMPORTANT: The load function can only load from classpath; and not the
file-system in general. This is on purpose
to avoid potential security risks by constructing dynamic simple expressions
using the load function to attempt
-to load server files.
+to load server files. A prefix such as `file:` is not used:
`$\{load(file:/var/inbox/data.json)}` looks for
+`/var/inbox/data.json` on the classpath, and when it is not there, the error
says so.
+
+With Camel JBang, `camel run` puts the files of the project on the classpath,
such as a `stock.json` next to the
+routes, so `$\{load(stock.json)}` reads it there. To read a file of the
file-system in any runtime, use the
+xref:constant-language.adoc[Constant] language with `resource:file:`, such as
`resource:file:stock.json`, for example
+in a `setVariable` that keeps the content for the steps after it.
=== String Functions
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
index 6e497e5dc05f..c036e441967f 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.language.simple;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -896,6 +897,26 @@ public final class MiscExpressionBuilder {
};
}
+ /**
+ * The load function reads the classpath only, so a prefix such as file:
is not used:
+ * ${load(file:/var/inbox/data.json)} looks for /var/inbox/data.json on
the classpath. When it is not there, the
+ * error says so, and for file: what reads a file of the file-system.
+ */
+ private static FileNotFoundException
notOnTheClasspath(FileNotFoundException e, String scheme, String part) {
+ if (scheme == null || "classpath".equals(scheme)) {
+ return e;
+ }
+ String message = e.getMessage() + ". The load function loads from the
classpath only, so the " + scheme
+ + ": prefix is not used"
+ + ("file".equals(scheme)
+ ? "; to read a file of the file-system, use
the constant language with resource:file:"
+ + part
+ : "");
+ FileNotFoundException answer = new FileNotFoundException(message);
+ answer.initCause(e);
+ return answer;
+ }
+
/**
* Loads the given resource from classpath
*/
@@ -916,6 +937,7 @@ public final class MiscExpressionBuilder {
if (name != null) {
name = name.trim();
String part = StringHelper.after(name, ":", name);
+ String scheme = name.indexOf(':') > 0 ? name.substring(0,
name.indexOf(':')) : null;
boolean optional = part.endsWith("?optional=true");
if (optional) {
part = part.substring(0, part.length() - 14);
@@ -934,6 +956,8 @@ public final class MiscExpressionBuilder {
return null;
}
return IOHelper.loadText(is, false);
+ } catch (FileNotFoundException e) {
+ throw
RuntimeCamelException.wrapRuntimeException(notOnTheClasspath(e, scheme, part));
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeException(e);
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 9ebb4de8394c..c77072ad8ea9 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -2872,6 +2872,22 @@ public class SimpleTest extends LanguageTestSupport {
assertEquals("The name is ${body}", s);
}
+ @Test
+ public void testLoadWithAFilePrefixReadsTheClasspath() {
+ // the load function reads the classpath only: a file: prefix is not
used
+ Expression expression =
context.resolveLanguage("simple").createExpression("${load(file:mysimple.txt)}");
+ assertEquals("The name is ${body}", expression.evaluate(exchange,
String.class));
+
+ // and when the file is not on the classpath, the error says so, and
what reads a file of the file-system
+ Expression missing =
context.resolveLanguage("simple").createExpression("${load(file:target/data/stock.json)}");
+ Exception e = assertThrows(Exception.class, () ->
missing.evaluate(exchange, String.class));
+ assertIsInstanceOf(FileNotFoundException.class, e.getCause());
+ String message = e.getCause().getMessage();
+ assertTrue(message.contains("The load function loads from the
classpath only, so the file: prefix is not used"),
+ message);
+ assertTrue(message.contains("use the constant language with
resource:file:target/data/stock.json"), message);
+ }
+
@Test
public void testTrim() {
exchange.getMessage().setBody(" Hello World ");
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index 208883f172cc..f780f0426006 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -1669,8 +1669,14 @@ public final class CatalogDocs {
if (model.isProducerOnly()) {
return "producer only: use it as a to: (send to it); it cannot
start a route";
}
- return "from: consumes (reads or receives, starts a route); to:
produces (writes or sends); to consume one message"
- + " in the middle of a route use the poll EIP (poll: {uri:
...}), or pollEnrich";
+ String answer = "from: consumes (reads or receives, starts a route);
to: produces (writes or sends); to consume one"
+ + " message in the middle of a route use the poll EIP
(poll: {uri: ...}), or pollEnrich";
+ if ("file".equals(model.getScheme())) {
+ // a lookup table the route needs is read, not consumed: the poll
EIP moves the file when it has no noop
+ answer += "; to read a data file the route needs, such as a lookup
table, use the constant language with"
+ + " resource:file: (such as setVariable with constant:
resource:file:stock.json)";
+ }
+ return answer;
}
/** How many options of a component are listed in full when no filter is
given (CAMEL-25541). */
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 801e9e50addf..2d5410a8a8ee 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
@@ -792,7 +792,12 @@ final class EndpointChecks {
errors.add(linePrefix(optionLineMap.getOrDefault(name,
uriLineIdx))
+ scheme + ": Option '" + name + "' is not
applicable in producer only mode (to: sends to the"
+ " endpoint, for " + scheme + ": it writes the
body; to read from an endpoint in the middle of a"
- + " route use the poll EIP, or pollEnrich, see
camel_catalog_sample poll)");
+ + " route use the poll EIP, or pollEnrich, see
camel_catalog_sample poll"
+ + ("file".equals(scheme)
+ ? "; to read a data file the route needs,
such as a lookup table: setVariable with"
+ + " constant: resource:file:<path>"
+ : "")
+ + ")");
}
}
}
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 b64b7f94305e..17582bf927e6 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
@@ -344,14 +344,31 @@ class SourceValidatorEndpointTest {
org.assertj.core.api.Assertions.assertThat(msgs.get(0))
.contains("not applicable in producer only mode")
.contains("it writes the body")
- .contains("poll EIP");
+ .contains("poll EIP")
+ // a data file the route needs is read, not consumed
+ .contains("to read a data file the route needs, such as a
lookup table: setVariable with constant:"
+ + " resource:file:<path>");
+
+ // another component does not get the file's hint
+ String kafka = """
+ - from:
+ uri: timer:tick
+ steps:
+ - to:
+ uri: "kafka:orders?groupId=orders"
+ """;
+ org.assertj.core.api.Assertions
+ .assertThat(SourceValidator.validateYamlEndpoints(kafka, new
org.apache.camel.catalog.DefaultCamelCatalog()))
+ .singleElement().asString().contains("poll
EIP").doesNotContain("resource:file");
}
@org.junit.jupiter.api.Test
void componentDocSaysWhereItGoes() {
org.apache.camel.catalog.CamelCatalog catalog = new
org.apache.camel.catalog.DefaultCamelCatalog();
org.assertj.core.api.Assertions.assertThat(CatalogDocs.usage(catalog.componentModel("file"))).contains("from:
consumes")
- .contains("poll EIP");
+ .contains("poll EIP").contains("setVariable with constant:
resource:file:stock.json");
+
org.assertj.core.api.Assertions.assertThat(CatalogDocs.usage(catalog.componentModel("jms"))).contains("poll
EIP")
+ .doesNotContain("resource:file");
org.assertj.core.api.Assertions.assertThat(CatalogDocs.usage(catalog.componentModel("timer")))
.startsWith("consumer only");
org.assertj.core.api.Assertions.assertThat(CatalogDocs.usage(catalog.componentModel("log")))