This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24093
in repository https://gitbox.apache.org/repos/asf/camel.git

commit c9f1b35f0568b191c479f2513fedf44c372d3c9e
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 15 20:54:00 2026 +0200

    CAMEL-24093: camel-file - idempotent read-lock strategies must use original 
path as key
    
    Fix two bugs in FileIdempotentRepositoryReadLockStrategy,
    FileIdempotentChangedRepositoryReadLockStrategy, and
    FileIdempotentRenameRepositoryReadLockStrategy:
    
    1. asKey() used file.getAbsoluteFilePath() which reflects the post-preMove
       path. The acquire call uses the original path, but release computes from
       the renamed path - keys leak and same-named files are never consumed
       again. Now uses getCopyFromAbsoluteFilePath() when available, matching
       the pattern already used by MarkerFileExclusiveReadLockStrategy and
       GenericFileHelper.
    
    2. releaseExclusiveReadLockOnAbort was a no-op (or only delegated to the
       inner strategy), so an abort after successful acquire leaked the
       idempotent key permanently.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 ...dempotentChangedRepositoryReadLockStrategy.java | 11 +++-
 ...IdempotentRenameRepositoryReadLockStrategy.java | 11 +++-
 .../FileIdempotentRepositoryReadLockStrategy.java  | 12 ++--
 .../FileIdempotentReadLockPreMoveTest.java         | 77 ++++++++++++++++++++++
 4 files changed, 101 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentChangedRepositoryReadLockStrategy.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentChangedRepositoryReadLockStrategy.java
index 506d3c0b0666..945277336efc 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentChangedRepositoryReadLockStrategy.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentChangedRepositoryReadLockStrategy.java
@@ -115,6 +115,8 @@ public class 
FileIdempotentChangedRepositoryReadLockStrategy extends ServiceSupp
     public void releaseExclusiveReadLockOnAbort(
             GenericFileOperations<File> operations, GenericFile<File> file, 
Exchange exchange)
             throws Exception {
+        String key = asKey(exchange, file);
+        idempotentRepository.remove(exchange, key);
         changed.releaseExclusiveReadLockOnAbort(operations, file, exchange);
     }
 
@@ -313,9 +315,12 @@ public class 
FileIdempotentChangedRepositoryReadLockStrategy extends ServiceSupp
     }
 
     protected String asKey(Exchange exchange, GenericFile<File> file) {
-        // use absolute file path as default key, but evaluate if an expression
-        // key was configured
-        String key = file.getAbsoluteFilePath();
+        // use the copy from absolute path as that was the original path of the
+        // file when the lock was acquired
+        // for example if the file consumer uses preMove then the file is moved
+        // and therefore has another name that would no longer match
+        String key = file.getCopyFromAbsoluteFilePath() != null
+                ? file.getCopyFromAbsoluteFilePath() : 
file.getAbsoluteFilePath();
         if (endpoint.getIdempotentKey() != null) {
             Exchange dummy = GenericFileHelper.createDummy(endpoint, exchange, 
() -> file);
             key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRenameRepositoryReadLockStrategy.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRenameRepositoryReadLockStrategy.java
index 62538cf86a51..72ca3c2a866a 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRenameRepositoryReadLockStrategy.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRenameRepositoryReadLockStrategy.java
@@ -107,6 +107,8 @@ public class FileIdempotentRenameRepositoryReadLockStrategy 
extends ServiceSuppo
     public void releaseExclusiveReadLockOnAbort(
             GenericFileOperations<File> operations, GenericFile<File> file, 
Exchange exchange)
             throws Exception {
+        String key = asKey(exchange, file);
+        idempotentRepository.remove(exchange, key);
         rename.releaseExclusiveReadLockOnAbort(operations, file, exchange);
     }
 
@@ -227,9 +229,12 @@ public class 
FileIdempotentRenameRepositoryReadLockStrategy extends ServiceSuppo
     }
 
     protected String asKey(Exchange exchange, GenericFile<File> file) {
-        // use absolute file path as default key, but evaluate if an expression
-        // key was configured
-        String key = file.getAbsoluteFilePath();
+        // use the copy from absolute path as that was the original path of the
+        // file when the lock was acquired
+        // for example if the file consumer uses preMove then the file is moved
+        // and therefore has another name that would no longer match
+        String key = file.getCopyFromAbsoluteFilePath() != null
+                ? file.getCopyFromAbsoluteFilePath() : 
file.getAbsoluteFilePath();
         if (endpoint.getIdempotentKey() != null) {
             Exchange dummy = GenericFileHelper.createDummy(endpoint, exchange, 
() -> file);
             key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRepositoryReadLockStrategy.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRepositoryReadLockStrategy.java
index abbf18f83618..94c124619af2 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRepositoryReadLockStrategy.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/FileIdempotentRepositoryReadLockStrategy.java
@@ -95,7 +95,8 @@ public class FileIdempotentRepositoryReadLockStrategy extends 
ServiceSupport
     public void releaseExclusiveReadLockOnAbort(
             GenericFileOperations<File> operations, GenericFile<File> file, 
Exchange exchange)
             throws Exception {
-        // noop
+        String key = asKey(exchange, file);
+        idempotentRepository.remove(exchange, key);
     }
 
     @Override
@@ -276,9 +277,12 @@ public class FileIdempotentRepositoryReadLockStrategy 
extends ServiceSupport
     }
 
     protected String asKey(Exchange exchange, GenericFile<File> file) {
-        // use absolute file path as default key, but evaluate if an expression
-        // key was configured
-        String key = file.getAbsoluteFilePath();
+        // use the copy from absolute path as that was the original path of the
+        // file when the lock was acquired
+        // for example if the file consumer uses preMove then the file is moved
+        // and therefore has another name that would no longer match
+        String key = file.getCopyFromAbsoluteFilePath() != null
+                ? file.getCopyFromAbsoluteFilePath() : 
file.getAbsoluteFilePath();
         if (endpoint.getIdempotentKey() != null) {
             Exchange dummy = GenericFileHelper.createDummy(endpoint, exchange, 
() -> file);
             key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/file/strategy/FileIdempotentReadLockPreMoveTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/file/strategy/FileIdempotentReadLockPreMoveTest.java
new file mode 100644
index 000000000000..f4102a442878
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/file/strategy/FileIdempotentReadLockPreMoveTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.file.strategy;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.Registry;
+import 
org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that idempotent read lock correctly removes the key on rollback when 
preMove is used. Before the fix, the
+ * release key was computed from the post-preMove path, which didn't match the 
acquire key (original path), so the
+ * original key leaked in the repository and same-named files were never 
consumed again.
+ */
+public class FileIdempotentReadLockPreMoveTest extends ContextTestSupport {
+
+    final MemoryIdempotentRepository myRepo = new MemoryIdempotentRepository();
+
+    @Override
+    protected Registry createCamelRegistry() throws Exception {
+        Registry jndi = super.createCamelRegistry();
+        jndi.bind("myRepo", myRepo);
+        return jndi;
+    }
+
+    @Test
+    public void testIdempotentReadLockKeyRemovedOnRollbackWithPreMove() throws 
Exception {
+        assertEquals(0, myRepo.getCacheSize());
+
+        NotifyBuilder notify = new NotifyBuilder(context).whenDone(1).create();
+
+        template.sendBodyAndHeader(fileUri(), "Hello World", 
Exchange.FILE_NAME, "hello.txt");
+
+        assertTrue(notify.matches(10, TimeUnit.SECONDS));
+
+        // the route throws an exception so the file is rolled back
+        // with readLockRemoveOnRollback=true (default), the key must be 
removed
+        // from the repository so the file can be retried
+        assertEquals(0, myRepo.getCacheSize());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                errorHandler(noErrorHandler());
+
+                from(fileUri(
+                        
"?initialDelay=0&delay=10&readLock=idempotent&idempotentRepository=#myRepo&preMove=work&moveFailed=error"))
+                        .throwException(new IllegalStateException("Forced 
failure"));
+            }
+        };
+    }
+}

Reply via email to