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"));
+ }
+ };
+ }
+}