This is an automated email from the ASF dual-hosted git repository.
acosentino 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 a5385bd3648 CAMEL-22440 - Camel-AWS2-S3 Streaming upload: Add a
timestamp naming strategy (#19309)
a5385bd3648 is described below
commit a5385bd364868cd162add8144d4619024ef274cb
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 24 11:57:27 2025 +0200
CAMEL-22440 - Camel-AWS2-S3 Streaming upload: Add a timestamp naming
strategy (#19309)
* CAMEL-22440 - Camel-AWS2-S3 Streaming upload: Add a timestamp naming
strategy
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-22440 - Camel-AWS2-S3 Streaming upload: Add a timestamp naming
strategy
Signed-off-by: Andrea Cosentino <[email protected]>
* Regen
Signed-off-by: Andrea Cosentino <[email protected]>
---------
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../apache/camel/catalog/components/aws2-s3.json | 4 +-
.../apache/camel/component/aws2/s3/aws2-s3.json | 4 +-
.../aws2/s3/stream/AWS2S3StreamUploadProducer.java | 23 +++-
.../aws2/s3/stream/AWSS3NamingStrategyEnum.java | 3 +-
.../S3StreamUploadTimestampNamingStrategyIT.java | 117 +++++++++++++++++++
.../S3StreamUploadTimestampTimeoutIT.java | 117 +++++++++++++++++++
.../aws2/s3/stream/AWSS3NamingStrategyTest.java | 129 +++++++++++++++++++++
7 files changed, 390 insertions(+), 7 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3.json
index 169e659c57e..e2945ec0b23 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3.json
@@ -57,7 +57,7 @@
"keyName": { "index": 30, "kind": "property", "displayName": "Key Name",
"group": "producer", "label": "producer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Setting the key name for an element in the
bucket through endpoint parameter" },
"lazyStartProducer": { "index": 31, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fai [...]
"multiPartUpload": { "index": 32, "kind": "property", "displayName":
"Multi Part Upload", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "If it is true, camel
will upload the file with multipart format. The part size [...]
- "namingStrategy": { "index": 33, "kind": "property", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration" [...]
+ "namingStrategy": { "index": 33, "kind": "property", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random", "timestamp" ], "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"c [...]
"operation": { "index": 34, "kind": "property", "displayName":
"Operation", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.AWS2S3Operations", "enum": [ "copyObject",
"listObjects", "deleteObject", "deleteBucket", "listBuckets", "getObject",
"getObjectRange", "createDownloadLink", "headBucket", "headObject" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.c [...]
"partSize": { "index": 35, "kind": "property", "displayName": "Part Size",
"group": "producer", "label": "producer", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 26214400, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Set up the partSize which is used in multipart
upload, the default size is 25 MB. The minimum [...]
"restartingPolicy": { "index": 36, "kind": "property", "displayName":
"Restarting Policy", "group": "producer", "label": "producer", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3RestartingPolicyEnum", "enum":
[ "override", "lastPart" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "override", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuratio [...]
@@ -166,7 +166,7 @@
"deleteAfterWrite": { "index": 36, "kind": "parameter", "displayName":
"Delete After Write", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "Delete file object after
the S3 file has been uploaded" },
"keyName": { "index": 37, "kind": "parameter", "displayName": "Key Name",
"group": "producer", "label": "producer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Setting the key name for an element in the
bucket through endpoint parameter" },
"multiPartUpload": { "index": 38, "kind": "parameter", "displayName":
"Multi Part Upload", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "If it is true, camel
will upload the file with multipart format. The part size [...]
- "namingStrategy": { "index": 39, "kind": "parameter", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration [...]
+ "namingStrategy": { "index": 39, "kind": "parameter", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random", "timestamp" ], "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
" [...]
"operation": { "index": 40, "kind": "parameter", "displayName":
"Operation", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.AWS2S3Operations", "enum": [ "copyObject",
"listObjects", "deleteObject", "deleteBucket", "listBuckets", "getObject",
"getObjectRange", "createDownloadLink", "headBucket", "headObject" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel. [...]
"partSize": { "index": 41, "kind": "parameter", "displayName": "Part
Size", "group": "producer", "label": "producer", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 26214400, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Set up the partSize which is used in multipart
upload, the default size is 25 MB. The minimum [...]
"restartingPolicy": { "index": 42, "kind": "parameter", "displayName":
"Restarting Policy", "group": "producer", "label": "producer", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3RestartingPolicyEnum", "enum":
[ "override", "lastPart" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "override", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configurati [...]
diff --git
a/components/camel-aws/camel-aws2-s3/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3/aws2-s3.json
b/components/camel-aws/camel-aws2-s3/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3/aws2-s3.json
index 169e659c57e..e2945ec0b23 100644
---
a/components/camel-aws/camel-aws2-s3/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3/aws2-s3.json
+++
b/components/camel-aws/camel-aws2-s3/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3/aws2-s3.json
@@ -57,7 +57,7 @@
"keyName": { "index": 30, "kind": "property", "displayName": "Key Name",
"group": "producer", "label": "producer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Setting the key name for an element in the
bucket through endpoint parameter" },
"lazyStartProducer": { "index": 31, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fai [...]
"multiPartUpload": { "index": 32, "kind": "property", "displayName":
"Multi Part Upload", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "If it is true, camel
will upload the file with multipart format. The part size [...]
- "namingStrategy": { "index": 33, "kind": "property", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration" [...]
+ "namingStrategy": { "index": 33, "kind": "property", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random", "timestamp" ], "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"c [...]
"operation": { "index": 34, "kind": "property", "displayName":
"Operation", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.AWS2S3Operations", "enum": [ "copyObject",
"listObjects", "deleteObject", "deleteBucket", "listBuckets", "getObject",
"getObjectRange", "createDownloadLink", "headBucket", "headObject" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.c [...]
"partSize": { "index": 35, "kind": "property", "displayName": "Part Size",
"group": "producer", "label": "producer", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 26214400, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Set up the partSize which is used in multipart
upload, the default size is 25 MB. The minimum [...]
"restartingPolicy": { "index": 36, "kind": "property", "displayName":
"Restarting Policy", "group": "producer", "label": "producer", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3RestartingPolicyEnum", "enum":
[ "override", "lastPart" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "override", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuratio [...]
@@ -166,7 +166,7 @@
"deleteAfterWrite": { "index": 36, "kind": "parameter", "displayName":
"Delete After Write", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "Delete file object after
the S3 file has been uploaded" },
"keyName": { "index": 37, "kind": "parameter", "displayName": "Key Name",
"group": "producer", "label": "producer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Setting the key name for an element in the
bucket through endpoint parameter" },
"multiPartUpload": { "index": 38, "kind": "parameter", "displayName":
"Multi Part Upload", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.aws2.s3.AWS2S3Configuration",
"configurationField": "configuration", "description": "If it is true, camel
will upload the file with multipart format. The part size [...]
- "namingStrategy": { "index": 39, "kind": "parameter", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration [...]
+ "namingStrategy": { "index": 39, "kind": "parameter", "displayName":
"Naming Strategy", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3NamingStrategyEnum", "enum": [
"progressive", "random", "timestamp" ], "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "progressive", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
" [...]
"operation": { "index": 40, "kind": "parameter", "displayName":
"Operation", "group": "producer", "label": "producer", "required": false,
"type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.AWS2S3Operations", "enum": [ "copyObject",
"listObjects", "deleteObject", "deleteBucket", "listBuckets", "getObject",
"getObjectRange", "createDownloadLink", "headBucket", "headObject" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel. [...]
"partSize": { "index": 41, "kind": "parameter", "displayName": "Part
Size", "group": "producer", "label": "producer", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 26214400, "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configuration", "description": "Set up the partSize which is used in multipart
upload, the default size is 25 MB. The minimum [...]
"restartingPolicy": { "index": 42, "kind": "parameter", "displayName":
"Restarting Policy", "group": "producer", "label": "producer", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.aws2.s3.stream.AWSS3RestartingPolicyEnum", "enum":
[ "override", "lastPart" ], "deprecated": false, "autowired": false, "secret":
false, "defaultValue": "override", "configurationClass":
"org.apache.camel.component.aws2.s3.AWS2S3Configuration", "configurationField":
"configurati [...]
diff --git
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWS2S3StreamUploadProducer.java
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWS2S3StreamUploadProducer.java
index 0834341cf4a..93baccd8248 100644
---
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWS2S3StreamUploadProducer.java
+++
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWS2S3StreamUploadProducer.java
@@ -193,8 +193,11 @@ public class AWS2S3StreamUploadProducer extends
DefaultProducer {
if (state.index == 1 &&
getConfiguration().getNamingStrategy().equals(AWSS3NamingStrategyEnum.random)) {
state.id = UUID.randomUUID();
}
+ if (state.index == 1 &&
getConfiguration().getNamingStrategy().equals(AWSS3NamingStrategyEnum.timestamp))
{
+ state.timestamp = System.currentTimeMillis();
+ }
state.dynamicKeyName = fileNameToUpload(fileName,
getConfiguration().getNamingStrategy(), extension,
- state.part, state.id);
+ state.part, state.id, state.timestamp);
CreateMultipartUploadRequest.Builder createMultipartUploadRequest
=
CreateMultipartUploadRequest.builder().bucket(getConfiguration().getBucketName())
.key(state.dynamicKeyName).checksumAlgorithm(algorithm);
@@ -318,7 +321,7 @@ public class AWS2S3StreamUploadProducer extends
DefaultProducer {
}
private String fileNameToUpload(
- String fileName, AWSS3NamingStrategyEnum strategy, String ext, int
part, UUID id) {
+ String fileName, AWSS3NamingStrategyEnum strategy, String ext, int
part, UUID id, long timestamp) {
String dynamicKeyName;
switch (strategy) {
case progressive:
@@ -351,6 +354,21 @@ public class AWS2S3StreamUploadProducer extends
DefaultProducer {
}
}
break;
+ case timestamp:
+ if (part > 0) {
+ if (ObjectHelper.isNotEmpty(ext)) {
+ dynamicKeyName = fileName + "-" + timestamp + ext;
+ } else {
+ dynamicKeyName = fileName + "-" + timestamp;
+ }
+ } else {
+ if (ObjectHelper.isNotEmpty(ext)) {
+ dynamicKeyName = fileName + ext;
+ } else {
+ dynamicKeyName = fileName;
+ }
+ }
+ break;
default:
throw new IllegalArgumentException("Unsupported operation");
}
@@ -414,6 +432,7 @@ public class AWS2S3StreamUploadProducer extends
DefaultProducer {
ByteArrayOutputStream buffer;
String dynamicKeyName;
UUID id;
+ long timestamp;
CreateMultipartUploadResponse initResponse;
UploadState() {
diff --git
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyEnum.java
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyEnum.java
index bd8b94beaf5..3b9c3f6b431 100644
---
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyEnum.java
+++
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyEnum.java
@@ -18,5 +18,6 @@ package org.apache.camel.component.aws2.s3.stream;
public enum AWSS3NamingStrategyEnum {
progressive,
- random
+ random,
+ timestamp
}
diff --git
a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampNamingStrategyIT.java
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampNamingStrategyIT.java
new file mode 100644
index 00000000000..914fb20d058
--- /dev/null
+++
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampNamingStrategyIT.java
@@ -0,0 +1,117 @@
+/*
+ * 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.aws2.s3.integration;
+
+import java.util.List;
+
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.aws2.s3.AWS2S3Constants;
+import org.apache.camel.component.aws2.s3.AWS2S3Operations;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.s3.model.S3Object;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class S3StreamUploadTimestampNamingStrategyIT extends Aws2S3Base {
+
+ @EndpointInject
+ private ProducerTemplate template;
+
+ @EndpointInject("mock:result")
+ private MockEndpoint result;
+
+ @Test
+ public void sendInWithTimestampNamingStrategy() throws Exception {
+ result.expectedMessageCount(1000);
+
+ long beforeUpload = System.currentTimeMillis();
+
+ for (int i = 0; i < 1000; i++) {
+ template.sendBody("direct:stream1", "TestData\n");
+ }
+
+ long afterUpload = System.currentTimeMillis();
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ Exchange ex = template.request("direct:listObjects", new Processor() {
+
+ @Override
+ public void process(Exchange exchange) {
+ exchange.getIn().setHeader(AWS2S3Constants.S3_OPERATION,
AWS2S3Operations.listObjects);
+ }
+ });
+
+ List<S3Object> resp = ex.getMessage().getBody(List.class);
+ assertEquals(40, resp.size());
+
+ // Verify that uploaded files use timestamp naming strategy
+ // Files should have names like: fileTest.txt, fileTest-<timestamp>.txt
+ boolean foundBaseFile = false;
+ boolean foundTimestampFile = false;
+
+ for (S3Object s3Object : resp) {
+ String key = s3Object.key();
+
+ if ("fileTest.txt".equals(key)) {
+ foundBaseFile = true;
+ } else if (key.startsWith("fileTest-") && key.endsWith(".txt")) {
+ foundTimestampFile = true;
+
+ // Extract timestamp from filename and verify it's within
expected range
+ String timestampStr = key.substring("fileTest-".length(),
key.length() - ".txt".length());
+ try {
+ long timestamp = Long.parseLong(timestampStr);
+ assertTrue(timestamp >= beforeUpload && timestamp <=
afterUpload,
+ "Timestamp " + timestamp + " should be between " +
beforeUpload + " and " + afterUpload);
+ } catch (NumberFormatException e) {
+ // This shouldn't happen with timestamp naming strategy
+ throw new AssertionError("Expected numeric timestamp in
filename: " + key, e);
+ }
+ }
+ }
+
+ assertTrue(foundBaseFile, "Should find base file (fileTest.txt)");
+ assertTrue(foundTimestampFile, "Should find timestamp-named files
(fileTest-<timestamp>.txt)");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ String awsEndpoint1
+ = String.format(
+
"aws2-s3://%s?autoCreateBucket=true&streamingUploadMode=true&keyName=fileTest.txt&batchMessageNumber=25&namingStrategy=timestamp",
+ name.get());
+
+ from("direct:stream1").to(awsEndpoint1).to("mock:result");
+
+ String awsEndpoint =
String.format("aws2-s3://%s?autoCreateBucket=true",
+ name.get());
+
+ from("direct:listObjects").to(awsEndpoint);
+ }
+ };
+ }
+}
diff --git
a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampTimeoutIT.java
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampTimeoutIT.java
new file mode 100644
index 00000000000..4776391db20
--- /dev/null
+++
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/integration/S3StreamUploadTimestampTimeoutIT.java
@@ -0,0 +1,117 @@
+/*
+ * 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.aws2.s3.integration;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.aws2.s3.AWS2S3Constants;
+import org.apache.camel.component.aws2.s3.AWS2S3Operations;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.s3.model.S3Object;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class S3StreamUploadTimestampTimeoutIT extends Aws2S3Base {
+
+ @EndpointInject
+ private ProducerTemplate template;
+
+ @EndpointInject("mock:result")
+ private MockEndpoint result;
+
+ @Test
+ public void sendInWithTimestampAndTimeout() throws Exception {
+
+ for (int i = 1; i <= 2; i++) {
+ int count = i * 23;
+
+ result.expectedMessageCount(count);
+
+ long beforeUpload = System.currentTimeMillis();
+
+ for (int j = 0; j < 23; j++) {
+ template.sendBody("direct:stream1", "TestData\n");
+ }
+
+ long afterUpload = System.currentTimeMillis();
+
+ Awaitility.await().atMost(11, TimeUnit.SECONDS)
+ .untilAsserted(() ->
MockEndpoint.assertIsSatisfied(context));
+
+ Awaitility.await().atMost(11, TimeUnit.SECONDS)
+ .untilAsserted(() -> {
+ Exchange ex = template.request("direct:listObjects",
this::process);
+
+ List<S3Object> resp =
ex.getMessage().getBody(List.class);
+ assertEquals(1, resp.size());
+
+ // Verify the uploaded file uses timestamp naming
strategy
+ S3Object s3Object = resp.get(0);
+ String key = s3Object.key();
+
+ // The file should either be the base name or have a
timestamp suffix
+ if ("fileTest.txt".equals(key)) {
+ // This is fine - it's the base file
+ } else if (key.startsWith("fileTest-") &&
key.endsWith(".txt")) {
+ // Extract and validate timestamp
+ String timestampStr =
key.substring("fileTest-".length(), key.length() - ".txt".length());
+ try {
+ long timestamp = Long.parseLong(timestampStr);
+ assertTrue(timestamp >= beforeUpload &&
timestamp <= afterUpload + 11000, // Allow extra time for timeout
+ "Timestamp " + timestamp + " should be
within expected range");
+ } catch (NumberFormatException e) {
+ throw new AssertionError("Expected numeric
timestamp in filename: " + key, e);
+ }
+ } else {
+ throw new AssertionError("Unexpected filename
format: " + key);
+ }
+ });
+ }
+ }
+
+ private void process(Exchange exchange) {
+ exchange.getIn().setHeader(AWS2S3Constants.S3_OPERATION,
AWS2S3Operations.listObjects);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ String awsEndpoint1
+ = String.format(
+
"aws2-s3://%s?autoCreateBucket=true&streamingUploadMode=true&keyName=fileTest.txt&batchMessageNumber=25&namingStrategy=timestamp&streamingUploadTimeout=10000",
+ name.get());
+
+ from("direct:stream1").to(awsEndpoint1).to("mock:result");
+
+ String awsEndpoint =
String.format("aws2-s3://%s?autoCreateBucket=true",
+ name.get());
+
+ from("direct:listObjects").to(awsEndpoint);
+ }
+ };
+ }
+}
diff --git
a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyTest.java
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyTest.java
new file mode 100644
index 00000000000..4c8912a90df
--- /dev/null
+++
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/stream/AWSS3NamingStrategyTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.aws2.s3.stream;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+import org.apache.camel.component.aws2.s3.AWS2S3Configuration;
+import org.apache.camel.component.aws2.s3.AWS2S3Endpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class AWSS3NamingStrategyTest extends CamelTestSupport {
+
+ @Test
+ public void testProgressiveNamingStrategy() throws Exception {
+ AWS2S3StreamUploadProducer producer = createProducer();
+
+ Method method = AWS2S3StreamUploadProducer.class.getDeclaredMethod(
+ "fileNameToUpload", String.class,
AWSS3NamingStrategyEnum.class, String.class, int.class, UUID.class,
+ long.class);
+ method.setAccessible(true);
+
+ String result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.progressive, ".txt", 0, null, 0L);
+ assertEquals("testFile.txt", result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.progressive, ".txt", 1, null, 0L);
+ assertEquals("testFile-1.txt", result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.progressive, null, 2, null, 0L);
+ assertEquals("testFile-2", result);
+ }
+
+ @Test
+ public void testRandomNamingStrategy() throws Exception {
+ AWS2S3StreamUploadProducer producer = createProducer();
+
+ Method method = AWS2S3StreamUploadProducer.class.getDeclaredMethod(
+ "fileNameToUpload", String.class,
AWSS3NamingStrategyEnum.class, String.class, int.class, UUID.class,
+ long.class);
+ method.setAccessible(true);
+
+ UUID testUuid =
UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+
+ String result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.random, ".txt", 0, testUuid, 0L);
+ assertEquals("testFile.txt", result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.random, ".txt", 1, testUuid, 0L);
+ assertEquals("testFile-123e4567-e89b-12d3-a456-426614174000.txt",
result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.random, null, 1, testUuid, 0L);
+ assertEquals("testFile-123e4567-e89b-12d3-a456-426614174000", result);
+ }
+
+ @Test
+ public void testTimestampNamingStrategy() throws Exception {
+ AWS2S3StreamUploadProducer producer = createProducer();
+
+ Method method = AWS2S3StreamUploadProducer.class.getDeclaredMethod(
+ "fileNameToUpload", String.class,
AWSS3NamingStrategyEnum.class, String.class, int.class, UUID.class,
+ long.class);
+ method.setAccessible(true);
+
+ long testTimestamp = 1632468273000L; // September 24, 2021 1:04:33 PM
GMT
+
+ String result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.timestamp, ".txt", 0, null,
+ testTimestamp);
+ assertEquals("testFile.txt", result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.timestamp, ".txt", 1, null,
+ testTimestamp);
+ assertEquals("testFile-1632468273000.txt", result);
+
+ result = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.timestamp, null, 1, null, testTimestamp);
+ assertEquals("testFile-1632468273000", result);
+ }
+
+ @Test
+ public void testTimestampNamingStrategyWithCurrentTime() throws Exception {
+ AWS2S3StreamUploadProducer producer = createProducer();
+
+ Method method = AWS2S3StreamUploadProducer.class.getDeclaredMethod(
+ "fileNameToUpload", String.class,
AWSS3NamingStrategyEnum.class, String.class, int.class, UUID.class,
+ long.class);
+ method.setAccessible(true);
+
+ long currentTime = System.currentTimeMillis();
+
+ String result
+ = (String) method.invoke(producer, "testFile",
AWSS3NamingStrategyEnum.timestamp, ".txt", 1, null, currentTime);
+
+ assertTrue(result.startsWith("testFile-"));
+ assertTrue(result.endsWith(".txt"));
+
+ String timestampPart = result.substring("testFile-".length(),
result.length() - ".txt".length());
+ long parsedTimestamp = Long.parseLong(timestampPart);
+
+ // Allow for small time difference during test execution
+ assertTrue(Math.abs(parsedTimestamp - currentTime) < 1000,
+ "Timestamp should be within 1 second of current time");
+ }
+
+ private AWS2S3StreamUploadProducer createProducer() {
+ AWS2S3Endpoint endpoint = Mockito.mock(AWS2S3Endpoint.class);
+ AWS2S3Configuration configuration = new AWS2S3Configuration();
+ when(endpoint.getConfiguration()).thenReturn(configuration);
+
+ return new AWS2S3StreamUploadProducer(endpoint);
+ }
+}