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 9972ed33b1f4 CAMEL-26654: camel-azure-storage-datalake - getFile 
writes only into an OutputStream body (#29268)
9972ed33b1f4 is described below

commit 9972ed33b1f41e4780be86fe28c2db2ea693a75f
Author: Federico Mariani <[email protected]>
AuthorDate: Thu Nov 5 20:50:15 2026 +0100

    CAMEL-26654: camel-azure-storage-datalake - getFile writes only into an 
OutputStream body (#29268)
    
    getFile used message.getBody(OutputStream.class), a type conversion: a
    java.io.File, a java.nio.file.Path or the GenericFile of a file consumer
    converted to a stream that truncates the file, and the remote file was
    then downloaded into it. Use the body only when it is an OutputStream,
    as CAMEL-25933 did for getBlob in camel-azure-storage-blob; any other
    body gets an InputStream of the file.
    
    Co-Authored-By: Claude <[email protected]>
---
 .../docs/azure-storage-datalake-component.adoc     |  7 +-
 .../docs/azure-storage-datalake-component.adoc     |  7 +-
 .../operations/DataLakeFileOperations.java         |  9 +--
 .../operations/DataLakeFileOperationTest.java      | 89 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  8 ++
 5 files changed, 108 insertions(+), 12 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-storage-datalake-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-storage-datalake-component.adoc
index a21964ee4631..71c222d6f59d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-storage-datalake-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-storage-datalake-component.adoc
@@ -150,7 +150,7 @@ For these operations, `accountName`, `fileSystemName` and 
`fileName` options are
 [width="100%", cols="10%,90%", options="header",]
 |===
 |Operation |Description
-|`getFile` | Get the contents of a file
+|`getFile` | Get the contents of a file. The content is written to the body 
when the body is an `OutputStream`; any other body is replaced by an 
`InputStream` of the content.
 |`downloadToFile` | Download the entire file from the file system into a path 
specified by fileDir.
 |`downloadLink` | Generate a download link for the specified file using Shared 
Access Signature (SAS).
 The expiration time to be set for the link can be specified otherwise 1 hour 
is taken as default.
@@ -435,8 +435,9 @@ from("direct:start")
     .to("mock:results");
 ----
 
-Or if the body is not set, the operation will give an `InputStream`, given 
that you have already registered for query acceleration
-in azure portal.
+Only a body that is an `OutputStream` is written to. Any other body, or no 
body, is replaced by an `InputStream` of the file content,
+which can be processed further downstream. So a `File`, `Path` or the file of 
a file consumer in the body is not written to:
+to save a file locally, use the `downloadToFile` operation, or a `file` 
endpoint after `getFile`.
 
 ._Java-only: reads the file content as an `InputStream`_
 [source,java]
diff --git 
a/components/camel-azure/camel-azure-storage-datalake/src/main/docs/azure-storage-datalake-component.adoc
 
b/components/camel-azure/camel-azure-storage-datalake/src/main/docs/azure-storage-datalake-component.adoc
index a21964ee4631..71c222d6f59d 100644
--- 
a/components/camel-azure/camel-azure-storage-datalake/src/main/docs/azure-storage-datalake-component.adoc
+++ 
b/components/camel-azure/camel-azure-storage-datalake/src/main/docs/azure-storage-datalake-component.adoc
@@ -150,7 +150,7 @@ For these operations, `accountName`, `fileSystemName` and 
`fileName` options are
 [width="100%", cols="10%,90%", options="header",]
 |===
 |Operation |Description
-|`getFile` | Get the contents of a file
+|`getFile` | Get the contents of a file. The content is written to the body 
when the body is an `OutputStream`; any other body is replaced by an 
`InputStream` of the content.
 |`downloadToFile` | Download the entire file from the file system into a path 
specified by fileDir.
 |`downloadLink` | Generate a download link for the specified file using Shared 
Access Signature (SAS).
 The expiration time to be set for the link can be specified otherwise 1 hour 
is taken as default.
@@ -435,8 +435,9 @@ from("direct:start")
     .to("mock:results");
 ----
 
-Or if the body is not set, the operation will give an `InputStream`, given 
that you have already registered for query acceleration
-in azure portal.
+Only a body that is an `OutputStream` is written to. Any other body, or no 
body, is replaced by an `InputStream` of the file content,
+which can be processed further downstream. So a `File`, `Path` or the file of 
a file consumer in the body is not written to:
+to save a file locally, use the `downloadToFile` operation, or a `file` 
endpoint after `getFile`.
 
 ._Java-only: reads the file content as an `InputStream`_
 [source,java]
diff --git 
a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
 
b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
index 117e18a85815..cd8dbc1bfb58 100644
--- 
a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
+++ 
b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
@@ -65,12 +65,9 @@ public class DataLakeFileOperations {
 
     public DataLakeOperationResponse getFile(final Exchange exchange) throws 
IOException {
         final Message message = DataLakeUtils.getInMessage(exchange);
-        final OutputStream outputStream;
-        if (ObjectHelper.isEmpty(message)) {
-            outputStream = null;
-        } else {
-            outputStream = message.getBody(OutputStream.class);
-        }
+        // only an OutputStream body is written to, not a body that converts 
to one (such as a File, which would be emptied)
+        final OutputStream outputStream
+                = message != null && message.getBody() instanceof OutputStream 
os ? os : null;
 
         final DataLakeFileClientWrapper fileClientWrapper = 
getFileClientWrapper(exchange);
         final FileRange fileRange = configurationProxy.getFileRange(exchange);
diff --git 
a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
 
b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
index d41945dd20b7..3197eb022494 100644
--- 
a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
+++ 
b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
@@ -18,10 +18,15 @@ package 
org.apache.camel.component.azure.storage.datalake.operations;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.OffsetDateTime;
 
 import com.azure.core.http.HttpHeaders;
@@ -30,6 +35,9 @@ import com.azure.storage.file.datalake.DataLakeFileClient;
 import 
com.azure.storage.file.datalake.models.DataLakeFileOpenInputStreamResult;
 import com.azure.storage.file.datalake.models.DataLakeRequestConditions;
 import com.azure.storage.file.datalake.models.FileRange;
+import com.azure.storage.file.datalake.models.FileReadAsyncResponse;
+import com.azure.storage.file.datalake.models.FileReadHeaders;
+import com.azure.storage.file.datalake.models.FileReadResponse;
 import com.azure.storage.file.datalake.models.PathInfo;
 import com.azure.storage.file.datalake.models.PathProperties;
 import com.azure.storage.file.datalake.options.DataLakeFileInputStreamOptions;
@@ -37,12 +45,14 @@ import org.apache.camel.Exchange;
 import org.apache.camel.component.azure.storage.datalake.DataLakeConfiguration;
 import org.apache.camel.component.azure.storage.datalake.DataLakeConstants;
 import 
org.apache.camel.component.azure.storage.datalake.client.DataLakeFileClientWrapper;
+import org.apache.camel.component.file.FileConsumer;
 import org.apache.camel.support.DefaultExchange;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -54,8 +64,11 @@ import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -65,6 +78,9 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 public class DataLakeFileOperationTest extends CamelTestSupport {
 
+    private static final String LOCAL_CONTENT = "local file content";
+    private static final String REMOTE_CONTENT = "remote file content";
+
     private DataLakeConfiguration configuration;
 
     @Mock
@@ -104,6 +120,79 @@ public class DataLakeFileOperationTest extends 
CamelTestSupport {
         assertNotNull(secondResponse.getHeaders());
     }
 
+    @Test
+    void testGetFileWithFileBodyDoesNotWriteIntoTheFile(@TempDir Path dir) 
throws IOException {
+        // a File body converts to an OutputStream, but only an OutputStream 
body is written to
+        final Path file = Files.writeString(dir.resolve("local.txt"), 
LOCAL_CONTENT);
+
+        assertGetFileDoesNotWriteIntoTheFile(file.toFile(), file);
+    }
+
+    @Test
+    void testGetFileWithPathBodyDoesNotWriteIntoTheFile(@TempDir Path dir) 
throws IOException {
+        final Path file = Files.writeString(dir.resolve("local.txt"), 
LOCAL_CONTENT);
+
+        assertGetFileDoesNotWriteIntoTheFile(file, file);
+    }
+
+    @Test
+    void testGetFileWithGenericFileBodyDoesNotWriteIntoTheFile(@TempDir Path 
dir) throws IOException {
+        // the body of an exchange of a file consumer
+        final Path file = Files.writeString(dir.resolve("local.txt"), 
LOCAL_CONTENT);
+
+        
assertGetFileDoesNotWriteIntoTheFile(FileConsumer.asGenericFile(dir.toString(), 
file.toFile(), null, false), file);
+    }
+
+    private void assertGetFileDoesNotWriteIntoTheFile(Object body, Path file) 
throws IOException {
+        when(client.openInputStream()).thenReturn(new 
ByteArrayInputStream(REMOTE_CONTENT.getBytes(StandardCharsets.UTF_8)));
+        // a stream over the file given to the download would get the remote 
content
+        lenient().when(client.downloadWithResponse(any(), any(), any(), any(), 
anyBoolean(), any()))
+                .thenAnswer(invocation -> download(invocation.getArgument(0)));
+
+        final Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(body);
+
+        final DataLakeOperationResponse response = new 
DataLakeFileOperations(configuration, client).getFile(exchange);
+
+        assertEquals(LOCAL_CONTENT, Files.readString(file));
+        assertEquals(REMOTE_CONTENT,
+                new String(((InputStream) response.getBody()).readAllBytes(), 
StandardCharsets.UTF_8));
+        verify(client, never()).downloadWithResponse(any(), any(), any(), 
any(), anyBoolean(), any());
+    }
+
+    @Test
+    void testGetFileWithOutputStreamBodyWritesIntoTheStream() throws 
IOException {
+        final ByteArrayOutputStream body = new ByteArrayOutputStream();
+        when(client.downloadWithResponse(same(body), any(), any(), any(), 
anyBoolean(), any()))
+                .thenAnswer(invocation -> download(invocation.getArgument(0)));
+
+        final Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(body);
+
+        final DataLakeOperationResponse response = new 
DataLakeFileOperations(configuration, client).getFile(exchange);
+
+        assertSame(body, response.getBody());
+        assertEquals(REMOTE_CONTENT, body.toString(StandardCharsets.UTF_8));
+        verify(client, never()).openInputStream();
+    }
+
+    @Test
+    void testGetFileWithoutBodyReturnsAnInputStream() throws IOException {
+        when(client.openInputStream()).thenReturn(new 
ByteArrayInputStream(REMOTE_CONTENT.getBytes(StandardCharsets.UTF_8)));
+
+        final DataLakeOperationResponse response
+                = new DataLakeFileOperations(configuration, 
client).getFile(new DefaultExchange(context));
+
+        assertEquals(REMOTE_CONTENT,
+                new String(((InputStream) response.getBody()).readAllBytes(), 
StandardCharsets.UTF_8));
+        verify(client, never()).downloadWithResponse(any(), any(), any(), 
any(), anyBoolean(), any());
+    }
+
+    private static FileReadResponse download(OutputStream outputStream) throws 
IOException {
+        outputStream.write(REMOTE_CONTENT.getBytes(StandardCharsets.UTF_8));
+        return new FileReadResponse(new FileReadAsyncResponse(null, 200, new 
HttpHeaders(), null, new FileReadHeaders()));
+    }
+
     @Test
     void testDownloadToFileRejectsPathTraversalName() {
         // a file name is influenced by whoever writes to the file system and 
may contain ../ segments;
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 8d76abb015b2..50ee81f51184 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -5084,6 +5084,14 @@ Remove the range header and options if your route needs 
the entire file.
 The streaming path also honors the `CamelAzureStorageDataLakeRequestCondition` 
header, whether or not a range
 is set. An ETag mismatch or an invalid lease can now fail a streaming read 
that previously ignored the condition.
 
+=== camel-azure-storage-datalake - getFile writes only into an OutputStream 
body
+
+The getFile operation now writes the file into the message body only when the 
body is an `OutputStream`. A body that
+only converts to one, such as a `java.io.File`, a `java.nio.file.Path` or the 
file of a file consumer, is replaced by an
+`InputStream` of the file, as any other body is. Previously such a file was 
overwritten with the remote file, also with
+`noop=true` on the file consumer. To save a file locally, use the 
downloadToFile operation, or a `file` endpoint after
+getFile.
+
 === camel-chatscript - the first message of a conversation is sent, and the 
headers are kept
 
 The first message of a ChatScript endpoint, and every message with 
`resetChat=true`, used to start the conversation

Reply via email to