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

Reply via email to