This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-25973-language-binary-script in repository https://gitbox.apache.org/repos/asf/camel.git
commit c994912c31ed3d4a51fd3ac2e6bb074231c58f04 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 16:00:51 2026 +0100 CAMEL-25973: camel-language - binary=true with a file: or classpath: script returns the content as a stream With binary=true, a script with a scheme but no resource: prefix (such as language:constant:classpath:data.bin?binary=true) opened the resource, did not read it, and then evaluated the text of the uri. The body was the uri text and the stream was never closed. With cacheScript=true the endpoint pre-loaded the content as a String expression, so the bytes went through a text conversion. The producer now returns the opened stream as the result, as for a resource: script, and closes it when transform=false. The endpoint no longer pre-loads the script as an expression when binary=true. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/catalog/docs/language-component.adoc | 44 ++++++++++++++++++++++ .../src/main/docs/language-component.adoc | 44 ++++++++++++++++++++++ .../camel/component/language/LanguageEndpoint.java | 5 ++- .../camel/component/language/LanguageProducer.java | 18 ++++++--- .../ConstantLanguageBinaryResourceTest.java | 40 ++++++++++++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/language-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/language-component.adoc index e7753b75dc56..fba87714eb43 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/language-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/language-component.adoc @@ -296,3 +296,47 @@ YAML:: uri: mock:result ---- ==== + +=== Binary content + +With `binary=true`, the content of the resource is not evaluated as a script, but is the result as a stream +(`java.io.InputStream`). This works with and without the `resource:` prefix, for example to return an image: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("language:constant:classpath:images/logo.png?binary=true") + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- +<route> + <from uri="direct:start"/> + <to uri="language:constant:classpath:images/logo.png?binary=true"/> + <to uri="mock:result"/> +</route> +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - to: + uri: language:constant:classpath:images/logo.png + parameters: + binary: true + - to: + uri: mock:result +---- +==== diff --git a/components/camel-language/src/main/docs/language-component.adoc b/components/camel-language/src/main/docs/language-component.adoc index e7753b75dc56..fba87714eb43 100644 --- a/components/camel-language/src/main/docs/language-component.adoc +++ b/components/camel-language/src/main/docs/language-component.adoc @@ -296,3 +296,47 @@ YAML:: uri: mock:result ---- ==== + +=== Binary content + +With `binary=true`, the content of the resource is not evaluated as a script, but is the result as a stream +(`java.io.InputStream`). This works with and without the `resource:` prefix, for example to return an image: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("language:constant:classpath:images/logo.png?binary=true") + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- +<route> + <from uri="direct:start"/> + <to uri="language:constant:classpath:images/logo.png?binary=true"/> + <to uri="mock:result"/> +</route> +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - to: + uri: language:constant:classpath:images/logo.png + parameters: + binary: true + - to: + uri: mock:result +---- +==== diff --git a/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageEndpoint.java b/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageEndpoint.java index d0b98a75153a..ca292fc0fca4 100644 --- a/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageEndpoint.java +++ b/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageEndpoint.java @@ -112,7 +112,8 @@ public class LanguageEndpoint extends ResourceEndpoint { if (resultTypeClass == null && resultType != null) { resultTypeClass = getCamelContext().getClassResolver().resolveMandatoryClass(resultType); } - if (cacheScript && expression == null && script != null) { + // binary content is not a script, so it is not pre-loaded as an expression + if (cacheScript && !binary && expression == null && script != null) { boolean external = script.startsWith("file:") || script.startsWith("http:"); if (!external) { // we can pre optimize this as the script can be loaded from classpath or registry etc @@ -127,7 +128,7 @@ public class LanguageEndpoint extends ResourceEndpoint { @Override public Producer createProducer() throws Exception { - if (cacheScript && expression == null && script != null) { + if (cacheScript && !binary && expression == null && script != null) { script = resolveScript(script); expression = language.createExpression(script, new Object[] { resultTypeClass }); expression.init(getCamelContext()); diff --git a/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageProducer.java b/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageProducer.java index fb478bbec88a..cc1f910361ac 100644 --- a/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageProducer.java +++ b/components/camel-language/src/main/java/org/apache/camel/component/language/LanguageProducer.java @@ -88,11 +88,16 @@ public class LanguageProducer extends DefaultProducer { is = ResourceHelper.resolveMandatoryResourceAsInputStream(getEndpoint().getCamelContext(), ref); } - if (is != null && !getEndpoint().isBinary()) { - try { - script = getEndpoint().getCamelContext().getTypeConverter().convertTo(String.class, exchange, is); - } finally { - IOHelper.close(is); + if (is != null) { + if (getEndpoint().isBinary()) { + // binary content is not a script, so the stream is the result as-is + script = null; + } else { + try { + script = getEndpoint().getCamelContext().getTypeConverter().convertTo(String.class, exchange, is); + } finally { + IOHelper.close(is); + } } } } @@ -132,6 +137,9 @@ public class LanguageProducer extends DefaultProducer { // set message body if transform is enabled if (getEndpoint().isTransform()) { exchange.getMessage().setBody(result); + } else if (result == is) { + // the binary content is not used, so close the stream + IOHelper.close(is); } } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/ConstantLanguageBinaryResourceTest.java b/core/camel-core/src/test/java/org/apache/camel/language/ConstantLanguageBinaryResourceTest.java index 91118a45d535..73406640c764 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/ConstantLanguageBinaryResourceTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/ConstantLanguageBinaryResourceTest.java @@ -16,11 +16,14 @@ */ package org.apache.camel.language; +import java.nio.file.Files; + import org.apache.camel.ContextTestSupport; import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -48,6 +51,36 @@ public class ConstantLanguageBinaryResourceTest extends ContextTestSupport { template.sendBodyAndHeader(fileUri(), data, Exchange.FILE_NAME, "savedlogo.jpeg"); } + @Test + public void testConstantBinaryClasspathWithoutResourcePrefix() { + byte[] data = template.requestBody("direct:noprefix", "", byte[].class); + assertNotNull(data); + assertEquals(10249, data.length); + } + + @Test + public void testConstantBinaryClasspathWithoutResourcePrefixCacheScript() { + byte[] data = template.requestBody("direct:noprefixcache", "", byte[].class); + assertNotNull(data); + assertEquals(10249, data.length); + + // the stream is opened again for the next message + data = template.requestBody("direct:noprefixcache", "", byte[].class); + assertNotNull(data); + assertEquals(10249, data.length); + } + + @Test + public void testConstantBinaryFileWithoutResourcePrefix() throws Exception { + // not valid UTF-8, so the content must not be read as text + byte[] content = new byte[] { 0, (byte) 0xFF, (byte) 0xFE, 1, 2 }; + Files.createDirectories(testDirectory()); + Files.write(testFile("bin.dat"), content); + + byte[] data = template.requestBody("direct:file", "", byte[].class); + assertArrayEquals(content, data); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -56,6 +89,13 @@ public class ConstantLanguageBinaryResourceTest extends ContextTestSupport { from("direct:default").to("language:constant:resource:org/apache/camel/logo.jpeg?binary=true"); from("direct:classpath").to("language:constant:resource:classpath:org/apache/camel/logo.jpeg?binary=true"); + + from("direct:file").to("language:constant:file:" + testFile("bin.dat") + "?binary=true"); + + from("direct:noprefix").to("language:constant:classpath:org/apache/camel/logo.jpeg?binary=true"); + + from("direct:noprefixcache") + .to("language:constant:classpath:org/apache/camel/logo.jpeg?binary=true&cacheScript=true"); } }; }
