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 ce9b4c8555e7 CAMEL-26060: camel-file-watch - Set CamelFileLastModified
to the last modified time of the file (#28361)
ce9b4c8555e7 is described below
commit ce9b4c8555e7323b5520c59a842d94a81f0f64c3
Author: Urmila Unni <[email protected]>
AuthorDate: Sun Oct 25 13:00:04 2026 +0530
CAMEL-26060: camel-file-watch - Set CamelFileLastModified to the last
modified time of the file (#28361)
Co-authored-by: Claude <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/catalog/components/file-watch.json | 2 +-
.../camel/catalog/docs/file-watch-component.adoc | 12 ++++----
.../camel/component/file/watch/file-watch.json | 2 +-
.../src/main/docs/file-watch-component.adoc | 12 ++++----
.../component/file/watch/FileWatchConstants.java | 3 +-
.../component/file/watch/FileWatchConsumer.java | 14 ++++++++-
.../file/watch/FileWatchComponentTest.java | 35 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 6 ++++
.../dsl/FileWatchEndpointBuilderFactory.java | 3 +-
9 files changed, 72 insertions(+), 17 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file-watch.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file-watch.json
index e0fb4ccc0731..2887f67075a7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file-watch.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file-watch.json
@@ -42,7 +42,7 @@
"CamelFileRelativePath": { "index": 6, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The relative path.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_RELATIVE_PATH" },
"CamelFileNameConsumed": { "index": 7, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the file that has been consumed",
"constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_NAME_CONSUMED" },
"CamelFileParent": { "index": 8, "kind": "header", "displayName": "",
"group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The parent path.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_PARENT" },
- "CamelFileLastModified": { "index": 9, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "A Long value containing the last modified timestamp of
the file.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_LAST_MODIFIED" }
+ "CamelFileLastModified": { "index": 9, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "A Long value containing the last modified timestamp of
the file. Not set when the file no longer exists, such as for a DELETE event.",
"constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_LAST_MODIFIED" }
},
"bodyTypes": {
"consumerOut": { "carriers": [ "file" ], "javaType": "java.io.File",
"description": "The java.io.File of the file or directory of the event, also
for DELETE, with the event in the CamelFileEventType header. The consumer does
not read it: it converts to text, bytes or a stream by reading the file, which
fails after a DELETE" }
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/file-watch-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/file-watch-component.adoc
index ef865b01dce4..ec1be4e66842 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/file-watch-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/file-watch-component.adoc
@@ -36,7 +36,7 @@ Java::
[source,java]
----
from("file-watch://some-directory")
- .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}");
+ .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}");
----
XML::
@@ -45,7 +45,7 @@ XML::
----
<route>
<from uri="file-watch://some-directory"/>
- <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}"/>
+ <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}"/>
</route>
----
@@ -58,7 +58,7 @@ YAML::
uri: file-watch://some-directory
steps:
- log:
- message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelFileLastModified}"
+ message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelMessageTimestamp}"
----
====
@@ -71,7 +71,7 @@ Java::
[source,java]
----
from("file-watch://some-directory?events=DELETE,CREATE&antInclude=**/*.txt")
- .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}");
+ .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}");
----
XML::
@@ -80,7 +80,7 @@ XML::
----
<route>
<from
uri="file-watch://some-directory?events=DELETE,CREATE&antInclude=**/*.txt"/>
- <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}"/>
+ <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}"/>
</route>
----
@@ -96,7 +96,7 @@ YAML::
antInclude: "**/*.txt"
steps:
- log:
- message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelFileLastModified}"
+ message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelMessageTimestamp}"
----
====
diff --git
a/components/camel-file-watch/src/generated/resources/META-INF/org/apache/camel/component/file/watch/file-watch.json
b/components/camel-file-watch/src/generated/resources/META-INF/org/apache/camel/component/file/watch/file-watch.json
index e0fb4ccc0731..2887f67075a7 100644
---
a/components/camel-file-watch/src/generated/resources/META-INF/org/apache/camel/component/file/watch/file-watch.json
+++
b/components/camel-file-watch/src/generated/resources/META-INF/org/apache/camel/component/file/watch/file-watch.json
@@ -42,7 +42,7 @@
"CamelFileRelativePath": { "index": 6, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The relative path.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_RELATIVE_PATH" },
"CamelFileNameConsumed": { "index": 7, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the file that has been consumed",
"constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_NAME_CONSUMED" },
"CamelFileParent": { "index": 8, "kind": "header", "displayName": "",
"group": "consumer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The parent path.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_PARENT" },
- "CamelFileLastModified": { "index": 9, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "A Long value containing the last modified timestamp of
the file.", "constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_LAST_MODIFIED" }
+ "CamelFileLastModified": { "index": 9, "kind": "header", "displayName":
"", "group": "consumer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "A Long value containing the last modified timestamp of
the file. Not set when the file no longer exists, such as for a DELETE event.",
"constantName":
"org.apache.camel.component.file.watch.FileWatchConstants#FILE_LAST_MODIFIED" }
},
"bodyTypes": {
"consumerOut": { "carriers": [ "file" ], "javaType": "java.io.File",
"description": "The java.io.File of the file or directory of the event, also
for DELETE, with the event in the CamelFileEventType header. The consumer does
not read it: it converts to text, bytes or a stream by reading the file, which
fails after a DELETE" }
diff --git
a/components/camel-file-watch/src/main/docs/file-watch-component.adoc
b/components/camel-file-watch/src/main/docs/file-watch-component.adoc
index ef865b01dce4..ec1be4e66842 100644
--- a/components/camel-file-watch/src/main/docs/file-watch-component.adoc
+++ b/components/camel-file-watch/src/main/docs/file-watch-component.adoc
@@ -36,7 +36,7 @@ Java::
[source,java]
----
from("file-watch://some-directory")
- .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}");
+ .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}");
----
XML::
@@ -45,7 +45,7 @@ XML::
----
<route>
<from uri="file-watch://some-directory"/>
- <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}"/>
+ <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}"/>
</route>
----
@@ -58,7 +58,7 @@ YAML::
uri: file-watch://some-directory
steps:
- log:
- message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelFileLastModified}"
+ message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelMessageTimestamp}"
----
====
@@ -71,7 +71,7 @@ Java::
[source,java]
----
from("file-watch://some-directory?events=DELETE,CREATE&antInclude=**/*.txt")
- .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}");
+ .log("File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}");
----
XML::
@@ -80,7 +80,7 @@ XML::
----
<route>
<from
uri="file-watch://some-directory?events=DELETE,CREATE&antInclude=**/*.txt"/>
- <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelFileLastModified}"/>
+ <log message="File event: ${header.CamelFileEventType} occurred on file
${header.CamelFileName} at ${header.CamelMessageTimestamp}"/>
</route>
----
@@ -96,7 +96,7 @@ YAML::
antInclude: "**/*.txt"
steps:
- log:
- message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelFileLastModified}"
+ message: "File event: ${header.CamelFileEventType} occurred on
file ${header.CamelFileName} at ${header.CamelMessageTimestamp}"
----
====
diff --git
a/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConstants.java
b/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConstants.java
index 6697fb65abdb..f8afcabdd87f 100644
---
a/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConstants.java
+++
b/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConstants.java
@@ -54,7 +54,8 @@ public final class FileWatchConstants {
public static final String FILE_NAME_CONSUMED =
Exchange.FILE_NAME_CONSUMED;
@Metadata(description = "The parent path.", javaType = "String")
public static final String FILE_PARENT = Exchange.FILE_PARENT;
- @Metadata(description = "A `Long` value containing the last modified
timestamp of the file.",
+ @Metadata(description = "A `Long` value containing the last modified
timestamp of the file."
+ + " Not set when the file no longer exists, such
as for a DELETE event.",
javaType = "long")
public static final String FILE_LAST_MODIFIED =
Exchange.FILE_LAST_MODIFIED;
diff --git
a/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConsumer.java
b/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConsumer.java
index 111f51e8c481..ecff70145fab 100644
---
a/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConsumer.java
+++
b/components/camel-file-watch/src/main/java/org/apache/camel/component/file/watch/FileWatchConsumer.java
@@ -171,12 +171,24 @@ public class FileWatchConsumer extends DefaultConsumer {
message.setHeader(FileWatchConstants.FILE_PARENT,
PathUtils.normalizeToString(event.getEventPath().getParent().toAbsolutePath()));
- message.setHeader(FileWatchConstants.FILE_LAST_MODIFIED,
event.getEventDate());
+ Long lastModified = lastModified(event.getEventPath());
+ if (lastModified != null) {
+ message.setHeader(FileWatchConstants.FILE_LAST_MODIFIED,
lastModified);
+ }
message.setHeader(Exchange.MESSAGE_TIMESTAMP, event.getEventDate());
return exchange;
}
+ private static Long lastModified(Path path) {
+ try {
+ return Files.getLastModifiedTime(path).toMillis();
+ } catch (IOException | SecurityException e) {
+ // the file no longer exists (IOException) or access is denied
(SecurityException)
+ return null;
+ }
+ }
+
private boolean matchFilters(FileEvent fileEvent) {
if (!events.isEmpty() && !events.contains(fileEvent.getEventType())) {
return false;
diff --git
a/components/camel-file-watch/src/test/java/org/apache/camel/component/file/watch/FileWatchComponentTest.java
b/components/camel-file-watch/src/test/java/org/apache/camel/component/file/watch/FileWatchComponentTest.java
index 2fc816b875fe..4667fcd60101 100644
---
a/components/camel-file-watch/src/test/java/org/apache/camel/component/file/watch/FileWatchComponentTest.java
+++
b/components/camel-file-watch/src/test/java/org/apache/camel/component/file/watch/FileWatchComponentTest.java
@@ -21,6 +21,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
import java.util.UUID;
import org.apache.camel.builder.RouteBuilder;
@@ -29,6 +31,7 @@ import org.apache.camel.component.mock.MockEndpoint;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
public class FileWatchComponentTest extends FileWatchComponentTestBase {
@@ -136,6 +139,35 @@ public class FileWatchComponentTest extends
FileWatchComponentTestBase {
MockEndpoint.assertIsSatisfied(context);
}
+ @Test
+ public void testLastModifiedHeaderIsTimeOfFile() throws Exception {
+ MockEndpoint watchCreate = getMockEndpoint("mock:watchCreate");
+ watchCreate.expectedMessageCount(1);
+
+ // create the file outside the watched directory with an old last
modified time, and move it in,
+ // so the last modified time of the file differs from the time of the
event
+ Path staging = Files.createDirectories(folder.resolve("staging"));
+ Path file = Files.writeString(staging.resolve("old.txt"), "Hello");
+ long lastModified =
Instant.parse("2020-01-01T00:00:00Z").toEpochMilli();
+ Files.setLastModifiedTime(file, FileTime.fromMillis(lastModified));
+ Files.move(file, Paths.get(testPath(), "old.txt"));
+
+ watchCreate.assertIsSatisfied();
+ assertEquals(lastModified,
+
watchCreate.getExchanges().get(0).getIn().getHeader(FileWatchConstants.FILE_LAST_MODIFIED,
Long.class));
+ }
+
+ @Test
+ public void testLastModifiedHeaderNotSetForDelete() throws Exception {
+ MockEndpoint watchDelete = getMockEndpoint("mock:watchDelete");
+ watchDelete.expectedMinimumMessageCount(1);
+
+ Files.delete(testFiles.get(0));
+
+ watchDelete.assertIsSatisfied();
+
assertNull(watchDelete.getExchanges().get(0).getIn().getHeader(FileWatchConstants.FILE_LAST_MODIFIED));
+ }
+
@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
@@ -167,6 +199,9 @@ public class FileWatchComponentTest extends
FileWatchComponentTestBase {
fromF("file-watch://%s?events=DELETE,MODIFY", testPath())
.to("mock:watchDeleteOrModify");
+
+ fromF("file-watch://%s?events=DELETE", testPath())
+ .to("mock:watchDelete");
}
};
}
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 15220435cce2..c4395b581d04 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
@@ -3463,3 +3463,9 @@ The server/listen and receipt/receive consumers have the
new `muteException` opt
route fails, the HTTP 500 that the partner gets says only that the message (or
the receipt) could not be processed.
Before, its body was the message of the exception of the route. Set
`muteException=false` to send the message of the
exception as before.
+
+=== camel-file-watch - CamelFileLastModified is the last modified time of the
file
+
+The `CamelFileLastModified` header is now the last modified time of the file,
as its documentation says.
+Before, it was the time of the event. The header is not set when the file no
longer exists, for example for a `DELETE` event.
+The time of the event is in the `CamelMessageTimestamp` header.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java
index fae071a1b802..e233fd8e0ae7 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java
@@ -628,7 +628,8 @@ public interface FileWatchEndpointBuilderFactory {
return "CamelFileParent";
}
/**
- * A Long value containing the last modified timestamp of the file.
+ * A Long value containing the last modified timestamp of the file. Not
+ * set when the file no longer exists, such as for a DELETE event.
*
* The option is a: {@code long} type.
*