This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch task-17 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 83dd20f462a64de1807c1bfc9cf0b73723d2bf37 Author: Andrea Cosentino <[email protected]> AuthorDate: Sun Mar 1 14:20:31 2026 +0100 CAMEL-23106 - camel-docling - Remove temp file in DoclingProducer.getInputPath() Signed-off-by: Andrea Cosentino <[email protected]> --- .../camel/component/docling/DoclingProducer.java | 17 ++++ .../docling/DoclingTempFileCleanupTest.java | 105 +++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java index e066933887bb..a1a04f6f70e9 100644 --- a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java +++ b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java @@ -72,6 +72,7 @@ import org.apache.camel.InvalidPayloadException; import org.apache.camel.WrappedFile; import org.apache.camel.support.DefaultProducer; import org.apache.camel.support.OAuthHelper; +import org.apache.camel.support.SynchronizationAdapter; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1506,6 +1507,7 @@ public class DoclingProducer extends DefaultProducer { // Treat as content to be written to a temp file Path tempFile = Files.createTempFile("docling-", ".tmp"); Files.write(tempFile, content.getBytes()); + registerTempFileCleanup(exchange, tempFile); validateFileSize(tempFile.toString()); return tempFile.toString(); } @@ -1515,6 +1517,7 @@ public class DoclingProducer extends DefaultProducer { } Path tempFile = Files.createTempFile("docling-", ".tmp"); Files.write(tempFile, content); + registerTempFileCleanup(exchange, tempFile); return tempFile.toString(); } else if (body instanceof File file) { validateFileSize(file.getAbsolutePath()); @@ -1524,6 +1527,20 @@ public class DoclingProducer extends DefaultProducer { throw new InvalidPayloadException(exchange, String.class); } + private void registerTempFileCleanup(Exchange exchange, Path tempFile) { + exchange.getExchangeExtension().addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(Exchange exchange) { + try { + Files.deleteIfExists(tempFile); + LOG.debug("Cleaned up temp file: {}", tempFile); + } catch (IOException e) { + LOG.warn("Failed to clean up temp file: {}", tempFile, e); + } + } + }); + } + private void validateFileSize(String filePath) throws IOException { Path path = Paths.get(filePath); if (Files.exists(path)) { diff --git a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingTempFileCleanupTest.java b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingTempFileCleanupTest.java new file mode 100644 index 000000000000..69c0178e19ff --- /dev/null +++ b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingTempFileCleanupTest.java @@ -0,0 +1,105 @@ +/* + * 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.component.docling; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies that temporary files created in {@code DoclingProducer.getInputPath()} are cleaned up after exchange + * processing completes. + * + * <p> + * Before the fix, temp files created for String content and byte[] bodies accumulated on disk indefinitely. After the + * fix, an {@code addOnCompletion} callback deletes them when the exchange finishes. + */ +class DoclingTempFileCleanupTest extends CamelTestSupport { + + @Test + void tempFileFromStringContentIsCleanedUp() throws Exception { + // Snapshot temp files before + List<Path> before = listDoclingTempFiles(); + + // Send string content (not a path, not a URL) — this triggers temp file creation. + // The docling CLI will fail (not installed), but the temp file cleanup + // runs on exchange completion regardless of success or failure. + try { + template.requestBody("direct:convert", "This is raw text content to convert"); + } catch (CamelExecutionException e) { + // Expected — docling binary not available in test env + } + + // After exchange completes, temp files should have been cleaned up + List<Path> after = listDoclingTempFiles(); + List<Path> leaked = new ArrayList<>(after); + leaked.removeAll(before); + + assertTrue(leaked.isEmpty(), + "Temp files leaked after exchange completion: " + leaked); + } + + @Test + void tempFileFromByteArrayIsCleanedUp() throws Exception { + List<Path> before = listDoclingTempFiles(); + + try { + template.requestBody("direct:convert", "Binary content for conversion".getBytes()); + } catch (CamelExecutionException e) { + // Expected — docling binary not available in test env + } + + List<Path> after = listDoclingTempFiles(); + List<Path> leaked = new ArrayList<>(after); + leaked.removeAll(before); + + assertTrue(leaked.isEmpty(), + "Temp files leaked after exchange completion: " + leaked); + } + + private List<Path> listDoclingTempFiles() throws IOException { + List<Path> result = new ArrayList<>(); + Path tmpDir = Path.of(System.getProperty("java.io.tmpdir")); + try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpDir, "docling-*.tmp")) { + for (Path entry : stream) { + result.add(entry); + } + } + return result; + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:convert") + .to("docling:convert?operation=CONVERT_TO_MARKDOWN"); + } + }; + } +}
