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 82507e3eab0b CAMEL-24093: camel-file - Idempotent read-lock strategies 
must use original path as key
82507e3eab0b is described below

commit 82507e3eab0b036f759dbede3fc7b5f02d09df6b
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 16 07:20:37 2026 +0200

    CAMEL-24093: camel-file - Idempotent read-lock strategies must use original 
path as key
    
    Fix two bugs in all three idempotent read-lock strategies
    (idempotent, idempotent-changed, idempotent-rename):
    
    1. asKey() used file.getAbsoluteFilePath() which reflects the
       post-preMove path. Acquire stores a key based on the original path
       but release computes from the renamed path — the original key leaks
       and same-named files are permanently skipped. Now uses
       getCopyFromAbsoluteFilePath() when available, matching the pattern
       used by MarkerFileExclusiveReadLockStrategy and GenericFileHelper.
    
    2. releaseExclusiveReadLockOnAbort was a no-op or only delegated to
       the inner strategy without touching the idempotent repository. An
       abort after successful acquire leaked the key permanently. All three
       strategies now properly remove the idempotent key on abort.
    
    Closes #24748
    
    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