You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/04/24 07:05:10 UTC
(camel) branch main updated: CAMEL-23328: Add support for blob index tags (set and get) to camel-azure-storage-blob (#22673)
This is an automated email from the ASF dual-hosted git repository.
oscerd 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 c683df608904 CAMEL-23328: Add support for blob index tags (set and get) to camel-azure-storage-blob (#22673)
c683df608904 is described below
commit c683df60890486e1ccd614461a606365ce56e903
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Fri Apr 24 09:05:04 2026 +0200
CAMEL-23328: Add support for blob index tags (set and get) to camel-azure-storage-blob (#22673)
Signed-off-by: Andrea Cosentino <an...@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../catalog/components/azure-storage-blob.json | 9 +-
.../azure/storage/blob/azure-storage-blob.json | 9 +-
.../main/docs/azure-storage-blob-component.adoc | 24 ++++++
.../azure/storage/blob/BlobConfiguration.java | 2 +-
.../blob/BlobConfigurationOptionsProxy.java | 4 +
.../azure/storage/blob/BlobConstants.java | 4 +
.../azure/storage/blob/BlobExchangeHeaders.java | 10 +++
.../storage/blob/BlobOperationsDefinition.java | 11 ++-
.../component/azure/storage/blob/BlobProducer.java | 6 ++
.../storage/blob/client/BlobClientWrapper.java | 23 +++++
.../storage/blob/operations/BlobOperations.java | 50 +++++++++++
.../storage/blob/integration/BlobOperationsIT.java | 99 ++++++++++++++++++++++
.../blob/operations/BlobOperationsTest.java | 80 +++++++++++++++++
.../endpoint/dsl/BlobEndpointBuilderFactory.java | 13 +++
14 files changed, 334 insertions(+), 10 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-storage-blob.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-storage-blob.json
index cb990329ee74..34329a989aa4 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-storage-blob.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-storage-blob.json
@@ -64,7 +64,7 @@
"lazyStartProducer": { "index": 37, "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 [...]
"maxConcurrency": { "index": 38, "kind": "property", "displayName": "Max Concurrency", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum number of parallel requests to use during upload with uploadBlockBlobChunk [...]
"maxSingleUploadSize": { "index": 39, "kind": "property", "displayName": "Max Single Upload Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum size in bytes for a single upload request with uploadBlockBlobChu [...]
- "operation": { "index": 40, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getB [...]
+ "operation": { "index": 40, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getB [...]
"pageBlobSize": { "index": 41, "kind": "property", "displayName": "Page Blob Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 512, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Specifies the maximum size for the page blob, up to 8 TB. The page blob [...]
"autowiredEnabled": { "index": 42, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...]
"healthCheckConsumerEnabled": { "index": 43, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
@@ -76,7 +76,7 @@
"sourceBlobAccessKey": { "index": 49, "kind": "property", "displayName": "Source Blob Access Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Source Blob Access Key: for copyblob operation, sadly, we need to have an acc [...]
},
"headers": {
- "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getBlobBloc [...]
+ "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getBlobBloc [...]
"CamelAzureStorageBlobHttpHeaders": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "BlobHttpHeaders", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(uploadBlockBlob, commitBlobBlockList, createAppendBlob, createPageBlob) Additional parameters for a set of operations.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_HTTP_HEA [...]
"CamelAzureStorageBlobETag": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The E Tag of the blob", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#E_TAG" },
"CamelAzureStorageBlobCreationTime": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creation time of the blob.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#CREATION_TIME" },
@@ -145,7 +145,8 @@
"CamelAzureStorageBlobChangeFeedStartTime": { "index": 66, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) It filters the results to return events approximately after the start time. Note: A few events belonging to the previous hour can also be returned. A few events belonging to this hour can [...]
"CamelAzureStorageBlobChangeFeedEndTime": { "index": 67, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) It filters the results to return events approximately before the end time. Note: A few events belonging to the next hour can also be returned. A few events belonging to this hour can be miss [...]
"CamelAzureStorageBlobContext": { "index": 68, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Context", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) This gives additional context that is passed through the Http pipeline during the service call.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#CHANGE_FEED_CONTEXT" },
- "CamelAzureStorageBlobSnapshotId": { "index": 69, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The snapshot identifier. On createBlobSnapshot it is set on the exchange as the id of the newly created snapshot. On read operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as input to target a specific blob [...]
+ "CamelAzureStorageBlobSnapshotId": { "index": 69, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The snapshot identifier. On createBlobSnapshot it is set on the exchange as the id of the newly created snapshot. On read operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as input to target a specific blob [...]
+ "CamelAzureStorageBlobTags": { "index": 70, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Map<String,String>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(producer) (setBlobTags) The tags to set on the blob as key-value pairs. (consumer) The tags retrieved from the blob.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_TAGS" }
},
"properties": {
"accountName": { "index": 0, "kind": "path", "displayName": "Account Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Azure account name to be used for authentication with azure blob services" },
@@ -193,7 +194,7 @@
"downloadLinkExpiration": { "index": 42, "kind": "parameter", "displayName": "Download Link Expiration", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Override the default expiration (millis) of URL download link." },
"maxConcurrency": { "index": 43, "kind": "parameter", "displayName": "Max Concurrency", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum number of parallel requests to use during upload with uploadBlockBlobChun [...]
"maxSingleUploadSize": { "index": 44, "kind": "parameter", "displayName": "Max Single Upload Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum size in bytes for a single upload request with uploadBlockBlobCh [...]
- "operation": { "index": 45, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "get [...]
+ "operation": { "index": 45, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "get [...]
"pageBlobSize": { "index": 46, "kind": "parameter", "displayName": "Page Blob Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 512, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Specifies the maximum size for the page blob, up to 8 TB. The page blo [...]
"lazyStartProducer": { "index": 47, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "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 produ [...]
"backoffErrorThreshold": { "index": 48, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." },
diff --git a/components/camel-azure/camel-azure-storage-blob/src/generated/resources/META-INF/org/apache/camel/component/azure/storage/blob/azure-storage-blob.json b/components/camel-azure/camel-azure-storage-blob/src/generated/resources/META-INF/org/apache/camel/component/azure/storage/blob/azure-storage-blob.json
index cb990329ee74..34329a989aa4 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/generated/resources/META-INF/org/apache/camel/component/azure/storage/blob/azure-storage-blob.json
+++ b/components/camel-azure/camel-azure-storage-blob/src/generated/resources/META-INF/org/apache/camel/component/azure/storage/blob/azure-storage-blob.json
@@ -64,7 +64,7 @@
"lazyStartProducer": { "index": 37, "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 [...]
"maxConcurrency": { "index": 38, "kind": "property", "displayName": "Max Concurrency", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum number of parallel requests to use during upload with uploadBlockBlobChunk [...]
"maxSingleUploadSize": { "index": 39, "kind": "property", "displayName": "Max Single Upload Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum size in bytes for a single upload request with uploadBlockBlobChu [...]
- "operation": { "index": 40, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getB [...]
+ "operation": { "index": 40, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getB [...]
"pageBlobSize": { "index": 41, "kind": "property", "displayName": "Page Blob Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 512, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Specifies the maximum size for the page blob, up to 8 TB. The page blob [...]
"autowiredEnabled": { "index": 42, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...]
"healthCheckConsumerEnabled": { "index": 43, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
@@ -76,7 +76,7 @@
"sourceBlobAccessKey": { "index": 49, "kind": "property", "displayName": "Source Blob Access Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Source Blob Access Key: for copyblob operation, sadly, we need to have an acc [...]
},
"headers": {
- "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getBlobBloc [...]
+ "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "getBlobBloc [...]
"CamelAzureStorageBlobHttpHeaders": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "BlobHttpHeaders", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(uploadBlockBlob, commitBlobBlockList, createAppendBlob, createPageBlob) Additional parameters for a set of operations.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_HTTP_HEA [...]
"CamelAzureStorageBlobETag": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The E Tag of the blob", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#E_TAG" },
"CamelAzureStorageBlobCreationTime": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creation time of the blob.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#CREATION_TIME" },
@@ -145,7 +145,8 @@
"CamelAzureStorageBlobChangeFeedStartTime": { "index": 66, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) It filters the results to return events approximately after the start time. Note: A few events belonging to the previous hour can also be returned. A few events belonging to this hour can [...]
"CamelAzureStorageBlobChangeFeedEndTime": { "index": 67, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "OffsetDateTime", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) It filters the results to return events approximately before the end time. Note: A few events belonging to the next hour can also be returned. A few events belonging to this hour can be miss [...]
"CamelAzureStorageBlobContext": { "index": 68, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Context", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(getChangeFeed) This gives additional context that is passed through the Http pipeline during the service call.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#CHANGE_FEED_CONTEXT" },
- "CamelAzureStorageBlobSnapshotId": { "index": 69, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The snapshot identifier. On createBlobSnapshot it is set on the exchange as the id of the newly created snapshot. On read operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as input to target a specific blob [...]
+ "CamelAzureStorageBlobSnapshotId": { "index": 69, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The snapshot identifier. On createBlobSnapshot it is set on the exchange as the id of the newly created snapshot. On read operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as input to target a specific blob [...]
+ "CamelAzureStorageBlobTags": { "index": 70, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Map<String,String>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "(producer) (setBlobTags) The tags to set on the blob as key-value pairs. (consumer) The tags retrieved from the blob.", "constantName": "org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_TAGS" }
},
"properties": {
"accountName": { "index": 0, "kind": "path", "displayName": "Account Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Azure account name to be used for authentication with azure blob services" },
@@ -193,7 +194,7 @@
"downloadLinkExpiration": { "index": 42, "kind": "parameter", "displayName": "Download Link Expiration", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Override the default expiration (millis) of URL download link." },
"maxConcurrency": { "index": 43, "kind": "parameter", "displayName": "Max Concurrency", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum number of parallel requests to use during upload with uploadBlockBlobChun [...]
"maxSingleUploadSize": { "index": 44, "kind": "parameter", "displayName": "Max Single Upload Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "The maximum size in bytes for a single upload request with uploadBlockBlobCh [...]
- "operation": { "index": 45, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "get [...]
+ "operation": { "index": 45, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition", "enum": [ "listBlobContainers", "createBlobContainer", "deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob", "downloadBlobToFile", "downloadLink", "uploadBlockBlob", "uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlockList", "get [...]
"pageBlobSize": { "index": 46, "kind": "parameter", "displayName": "Page Blob Size", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 512, "configurationClass": "org.apache.camel.component.azure.storage.blob.BlobConfiguration", "configurationField": "configuration", "description": "Specifies the maximum size for the page blob, up to 8 TB. The page blo [...]
"lazyStartProducer": { "index": 47, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "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 produ [...]
"backoffErrorThreshold": { "index": 48, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." },
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
index fd2a86c06489..5907a188d92a 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
@@ -165,6 +165,8 @@ and existing blocks together. Any blocks not specified in the block list and per
|`getPageBlobRanges`|`PageBlob`|Returns the list of valid page ranges for a page blob or snapshot of a page blob.
|`copyBlob`|`Common`|Copy a blob from one container to another one, even from different accounts.
|`createBlobSnapshot`|`Common`|Creates a read-only snapshot of a blob. The snapshot ID is returned in the `CamelAzureStorageBlobSnapshotId` header.
+|`setBlobTags`|`Common`|Sets user-defined index tags on a blob. Tags are key-value pairs that can be used to filter and query blobs across containers. Tags can be provided via the `CamelAzureStorageBlobTags` header or as the message body (`Map<String, String>`).
+|`getBlobTags`|`Common`|Retrieves user-defined index tags from a blob. The tags are returned as the message body (`Map<String, String>`) and also set in the `CamelAzureStorageBlobTags` header.
|===
Refer to the example section in this page to learn how to use these operations into your camel application.
@@ -740,6 +742,28 @@ from("direct:readSnapshot")
.to("mock:result");
--------------------------------------------------------------------------------
+- `setBlobTags`
+
+[source,java]
+--------------------------------------------------------------------------------
+
+from("direct:setBlobTags")
+ .setHeader(BlobConstants.BLOB_TAGS, constant(Map.of("status", "quarantine", "category", "document")))
+ .to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobTags&serviceClient=#client")
+ .to("mock:result");
+--------------------------------------------------------------------------------
+
+- `getBlobTags`
+
+[source,java]
+--------------------------------------------------------------------------------
+
+from("direct:getBlobTags")
+ .to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlobTags&serviceClient=#client")
+ .log("Tags: ${body}")
+ .to("mock:result");
+--------------------------------------------------------------------------------
+
=== SAS Token generation example
SAS Blob Container tokens can be generated programmatically or via Azure UI. To generate the token with java code, the following can be done:
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfiguration.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfiguration.java
index 9b9534dd6881..816b0581c3af 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfiguration.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfiguration.java
@@ -51,7 +51,7 @@ public class BlobConfiguration implements Cloneable {
@UriParam(label = "producer",
enums = "listBlobContainers,createBlobContainer,deleteBlobContainer,listBlobs,getBlob,deleteBlob,downloadBlobToFile,downloadLink,"
+ "uploadBlockBlob,uploadBlockBlobChunked,stageBlockBlobList,commitBlobBlockList,getBlobBlockList,createAppendBlob,commitAppendBlob,createPageBlob,uploadPageBlob,resizePageBlob,"
- + "clearPageBlob,getPageBlobRanges,getChangeFeed,copyBlob,createBlobSnapshot",
+ + "clearPageBlob,getPageBlobRanges,getChangeFeed,copyBlob,createBlobSnapshot,setBlobTags,getBlobTags",
defaultValue = "listBlobContainers")
private BlobOperationsDefinition operation = BlobOperationsDefinition.listBlobContainers;
@UriParam(label = "common")
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfigurationOptionsProxy.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfigurationOptionsProxy.java
index de7e68634af3..70f38b032a20 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfigurationOptionsProxy.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfigurationOptionsProxy.java
@@ -275,6 +275,10 @@ public class BlobConfigurationOptionsProxy {
return null;
}
+ public Map<String, String> getBlobTags(final Exchange exchange) {
+ return BlobExchangeHeaders.getBlobTagsFromHeaders(exchange);
+ }
+
public BlobConfiguration getConfiguration() {
return configuration;
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConstants.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConstants.java
index d936c26a7ca2..17b423a0c66a 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConstants.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConstants.java
@@ -288,6 +288,10 @@ public final class BlobConstants {
+ " downloadLink) it can be provided as input to target a specific blob snapshot.",
javaType = "String")
public static final String BLOB_SNAPSHOT_ID = HEADER_PREFIX + "SnapshotId";
+ @Metadata(description = "(producer) (setBlobTags) The tags to set on the blob as key-value pairs.\n"
+ + "(consumer) The tags retrieved from the blob.",
+ javaType = "Map<String,String>")
+ public static final String BLOB_TAGS = HEADER_PREFIX + "Tags";
private BlobConstants() {
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobExchangeHeaders.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobExchangeHeaders.java
index 75aa59eb50ad..e4be71c4eab7 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobExchangeHeaders.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobExchangeHeaders.java
@@ -485,4 +485,14 @@ public class BlobExchangeHeaders {
headers.put(BlobConstants.BLOB_SNAPSHOT_ID, snapshotId);
return this;
}
+
+ @SuppressWarnings("unchecked")
+ public static Map<String, String> getBlobTagsFromHeaders(final Exchange exchange) {
+ return getObjectFromHeaders(exchange, BlobConstants.BLOB_TAGS, Map.class);
+ }
+
+ public BlobExchangeHeaders blobTags(final Map<String, String> tags) {
+ headers.put(BlobConstants.BLOB_TAGS, tags);
+ return this;
+ }
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobOperationsDefinition.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobOperationsDefinition.java
index ecdac66f6c0a..927fcd995937 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobOperationsDefinition.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobOperationsDefinition.java
@@ -140,5 +140,14 @@ public enum BlobOperationsDefinition {
/**
* Creates a read-only snapshot of a blob. The snapshot ID is returned in the exchange headers.
*/
- createBlobSnapshot
+ createBlobSnapshot,
+ /**
+ * Sets user-defined index tags on a blob. Tags are key-value pairs that can be used to filter and query blobs
+ * across containers.
+ */
+ setBlobTags,
+ /**
+ * Retrieves user-defined index tags from a blob.
+ */
+ getBlobTags
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobProducer.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobProducer.java
index 601b8fec3c1c..e7e38a0e54f6 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobProducer.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobProducer.java
@@ -132,6 +132,12 @@ public class BlobProducer extends DefaultProducer {
case createBlobSnapshot:
setResponse(exchange, getBlobOperations(exchange).createBlobSnapshot(exchange));
break;
+ case setBlobTags:
+ setResponse(exchange, getBlobOperations(exchange).setBlobTags(exchange));
+ break;
+ case getBlobTags:
+ setResponse(exchange, getBlobOperations(exchange).getBlobTags(exchange));
+ break;
default:
throw new IllegalArgumentException("Unsupported operation");
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/client/BlobClientWrapper.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/client/BlobClientWrapper.java
index dca5c6254c4c..8d777e09a573 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/client/BlobClientWrapper.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/client/BlobClientWrapper.java
@@ -53,7 +53,9 @@ import com.azure.storage.blob.models.PageBlobRequestConditions;
import com.azure.storage.blob.models.PageRange;
import com.azure.storage.blob.models.PageRangeItem;
import com.azure.storage.blob.models.ParallelTransferOptions;
+import com.azure.storage.blob.options.BlobGetTagsOptions;
import com.azure.storage.blob.options.BlobParallelUploadOptions;
+import com.azure.storage.blob.options.BlobSetTagsOptions;
import com.azure.storage.blob.options.BlobUploadFromFileOptions;
import com.azure.storage.blob.options.BlockBlobSimpleUploadOptions;
import com.azure.storage.blob.options.ListPageRangesOptions;
@@ -347,6 +349,27 @@ public class BlobClientWrapper {
return new BlobClientWrapper(client.getSnapshotClient(snapshotId));
}
+ public Response<Void> setTags(
+ final Map<String, String> tags,
+ final BlobRequestConditions requestConditions,
+ final Duration timeout) {
+ BlobSetTagsOptions options = new BlobSetTagsOptions(tags);
+ if (requestConditions != null) {
+ options.setRequestConditions(requestConditions);
+ }
+ return client.setTagsWithResponse(options, timeout, Context.NONE);
+ }
+
+ public Response<Map<String, String>> getTags(
+ final BlobRequestConditions requestConditions,
+ final Duration timeout) {
+ BlobGetTagsOptions options = new BlobGetTagsOptions();
+ if (requestConditions != null) {
+ options.setRequestConditions(requestConditions);
+ }
+ return client.getTagsWithResponse(options, timeout, Context.NONE);
+ }
+
public BlobLeaseClient getLeaseClient() {
return new BlobLeaseClientBuilder().blobClient(client).buildClient();
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
index 915f3541726e..44bca0b38a1a 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
@@ -608,6 +608,56 @@ public class BlobOperations {
return BlobOperationResponse.create(snapshotClient.getSnapshotId(), exchangeHeaders.toMap());
}
+ @SuppressWarnings("unchecked")
+ public BlobOperationResponse setBlobTags(final Exchange exchange) {
+ ObjectHelper.notNull(exchange, MISSING_EXCHANGE);
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Setting tags on blob [{}] from exchange [{}]...", configurationProxy.getBlobName(exchange), exchange);
+ }
+
+ Map<String, String> tags = configurationProxy.getBlobTags(exchange);
+ if (tags == null) {
+ tags = exchange.getIn().getBody(Map.class);
+ }
+ if (tags == null || tags.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Tags must be specified either as the message body (Map<String,String>) or via the "
+ + BlobConstants.BLOB_TAGS + " header.");
+ }
+
+ final BlobCommonRequestOptions commonRequestOptions = getCommonRequestOptions(exchange);
+
+ final Response<Void> response = client.setTags(
+ tags,
+ commonRequestOptions.getBlobRequestConditions(),
+ commonRequestOptions.getTimeout());
+
+ final BlobExchangeHeaders exchangeHeaders = BlobExchangeHeaders.create()
+ .httpHeaders(response.getHeaders());
+
+ return BlobOperationResponse.createWithEmptyBody(exchangeHeaders.toMap());
+ }
+
+ public BlobOperationResponse getBlobTags(final Exchange exchange) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Getting tags from blob [{}] from exchange [{}]...", configurationProxy.getBlobName(exchange), exchange);
+ }
+
+ final BlobCommonRequestOptions commonRequestOptions = getCommonRequestOptions(exchange);
+
+ final Response<Map<String, String>> response = client.getTags(
+ commonRequestOptions.getBlobRequestConditions(),
+ commonRequestOptions.getTimeout());
+
+ final Map<String, String> tags = response.getValue();
+ final BlobExchangeHeaders exchangeHeaders = BlobExchangeHeaders.create()
+ .blobTags(tags)
+ .httpHeaders(response.getHeaders());
+
+ return BlobOperationResponse.create(tags, exchangeHeaders.toMap());
+ }
+
private DownloadRetryOptions getDownloadRetryOptions(final BlobConfigurationOptionsProxy configurationProxy) {
return new DownloadRetryOptions().setMaxRetryRequests(configurationProxy.getMaxRetryRequests());
}
diff --git a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/integration/BlobOperationsIT.java b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/integration/BlobOperationsIT.java
index d0d02b8abcea..1812e766075e 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/integration/BlobOperationsIT.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/integration/BlobOperationsIT.java
@@ -28,8 +28,10 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.SecureRandom;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import com.azure.core.http.rest.PagedIterable;
@@ -483,6 +485,103 @@ class BlobOperationsIT extends Base {
blobClientWrapper.delete(null, null, null);
}
+ @SuppressWarnings("unchecked")
+ @Test
+ void testSetAndGetBlobTags() {
+ final BlobClientWrapper blobClientWrapper = blobContainerClientWrapper.getBlobClientWrapper(randomBlobName);
+ final BlobOperations operations = new BlobOperations(configuration, blobClientWrapper);
+
+ final Map<String, String> tags = new HashMap<>();
+ tags.put("status", "quarantine");
+ tags.put("category", "document");
+ tags.put("priority", "high");
+
+ // set tags via header
+ final Exchange setExchange = new DefaultExchange(context);
+ setExchange.getIn().setHeader(BlobConstants.BLOB_TAGS, tags);
+
+ final BlobOperationResponse setResponse = operations.setBlobTags(setExchange);
+ assertNotNull(setResponse);
+ assertTrue((boolean) setResponse.getBody());
+
+ // get tags
+ final Exchange getExchange = new DefaultExchange(context);
+ final BlobOperationResponse getResponse = operations.getBlobTags(getExchange);
+
+ assertNotNull(getResponse);
+ assertNotNull(getResponse.getBody());
+
+ final Map<String, String> retrievedTags = (Map<String, String>) getResponse.getBody();
+ assertEquals(3, retrievedTags.size());
+ assertEquals("quarantine", retrievedTags.get("status"));
+ assertEquals("document", retrievedTags.get("category"));
+ assertEquals("high", retrievedTags.get("priority"));
+
+ // also verify tags are in exchange headers
+ assertEquals(retrievedTags, getResponse.getHeaders().get(BlobConstants.BLOB_TAGS));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void testSetBlobTagsFromBody() {
+ final BlobClientWrapper blobClientWrapper = blobContainerClientWrapper.getBlobClientWrapper(randomBlobName);
+ final BlobOperations operations = new BlobOperations(configuration, blobClientWrapper);
+
+ final Map<String, String> tags = new HashMap<>();
+ tags.put("owner", "test-user");
+ tags.put("scanned", "true");
+
+ // set tags via body
+ final Exchange setExchange = new DefaultExchange(context);
+ setExchange.getIn().setBody(tags);
+
+ final BlobOperationResponse setResponse = operations.setBlobTags(setExchange);
+ assertNotNull(setResponse);
+ assertTrue((boolean) setResponse.getBody());
+
+ // verify by getting tags back
+ final Exchange getExchange = new DefaultExchange(context);
+ final BlobOperationResponse getResponse = operations.getBlobTags(getExchange);
+
+ final Map<String, String> retrievedTags = (Map<String, String>) getResponse.getBody();
+ assertEquals(2, retrievedTags.size());
+ assertEquals("test-user", retrievedTags.get("owner"));
+ assertEquals("true", retrievedTags.get("scanned"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void testOverwriteBlobTags() {
+ final BlobClientWrapper blobClientWrapper = blobContainerClientWrapper.getBlobClientWrapper(randomBlobName);
+ final BlobOperations operations = new BlobOperations(configuration, blobClientWrapper);
+
+ // set initial tags
+ final Map<String, String> initialTags = new HashMap<>();
+ initialTags.put("status", "pending");
+
+ final Exchange setExchange1 = new DefaultExchange(context);
+ setExchange1.getIn().setHeader(BlobConstants.BLOB_TAGS, initialTags);
+ operations.setBlobTags(setExchange1);
+
+ // overwrite with new tags
+ final Map<String, String> newTags = new HashMap<>();
+ newTags.put("status", "processed");
+ newTags.put("result", "clean");
+
+ final Exchange setExchange2 = new DefaultExchange(context);
+ setExchange2.getIn().setHeader(BlobConstants.BLOB_TAGS, newTags);
+ operations.setBlobTags(setExchange2);
+
+ // verify only new tags are present (set replaces all tags)
+ final Exchange getExchange = new DefaultExchange(context);
+ final BlobOperationResponse getResponse = operations.getBlobTags(getExchange);
+
+ final Map<String, String> retrievedTags = (Map<String, String>) getResponse.getBody();
+ assertEquals(2, retrievedTags.size());
+ assertEquals("processed", retrievedTags.get("status"));
+ assertEquals("clean", retrievedTags.get("result"));
+ }
+
@Test
void testGetPageBlobRanges() throws Exception {
final BlobClientWrapper blobClientWrapper = blobContainerClientWrapper.getBlobClientWrapper("upload_test_file.txt");
diff --git a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
index 7a81ef752f4d..d1ff618813e4 100644
--- a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
+++ b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
@@ -255,6 +255,86 @@ class BlobOperationsTest extends CamelTestSupport {
verify(client, times(1)).withSnapshot(headerSnapshotId);
}
+ @Test
+ void testSetBlobTags() {
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header", "123");
+
+ when(client.setTags(any(), any(), any()))
+ .thenReturn(new ResponseBase<>(null, 204, httpHeaders, null, null));
+
+ final Map<String, String> tags = new HashMap<>();
+ tags.put("status", "quarantine");
+ tags.put("category", "document");
+
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setHeader(BlobConstants.BLOB_TAGS, tags);
+
+ final BlobOperations operations = new BlobOperations(configuration, client);
+ final BlobOperationResponse response = operations.setBlobTags(exchange);
+
+ assertNotNull(response);
+ assertTrue((boolean) response.getBody());
+ assertNotNull(response.getHeaders());
+ assertEquals("123", ((HttpHeaders) response.getHeaders().get(BlobConstants.RAW_HTTP_HEADERS))
+ .get("x-test-header").getValue());
+ }
+
+ @Test
+ void testSetBlobTagsFromBody() {
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header", "456");
+
+ when(client.setTags(any(), any(), any()))
+ .thenReturn(new ResponseBase<>(null, 204, httpHeaders, null, null));
+
+ final Map<String, String> tags = new HashMap<>();
+ tags.put("owner", "test-user");
+
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody(tags);
+
+ final BlobOperations operations = new BlobOperations(configuration, client);
+ final BlobOperationResponse response = operations.setBlobTags(exchange);
+
+ assertNotNull(response);
+ assertTrue((boolean) response.getBody());
+ }
+
+ @Test
+ void testSetBlobTagsWithNoTagsThrows() {
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody("not-a-map");
+
+ final BlobOperations operations = new BlobOperations(configuration, client);
+ assertThrows(IllegalArgumentException.class, () -> operations.setBlobTags(exchange));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void testGetBlobTags() {
+ final Map<String, String> tags = new HashMap<>();
+ tags.put("status", "clean");
+ tags.put("scannedBy", "antivirus");
+
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header", "789");
+
+ when(client.getTags(any(), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders, tags, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+
+ final BlobOperations operations = new BlobOperations(configuration, client);
+ final BlobOperationResponse response = operations.getBlobTags(exchange);
+
+ assertNotNull(response);
+ assertNotNull(response.getBody());
+ final Map<String, String> resultTags = (Map<String, String>) response.getBody();
+ assertEquals("clean", resultTags.get("status"));
+ assertEquals("antivirus", resultTags.get("scannedBy"));
+ assertEquals(tags, response.getHeaders().get(BlobConstants.BLOB_TAGS));
+ assertEquals("789", ((HttpHeaders) response.getHeaders().get(BlobConstants.RAW_HTTP_HEADERS))
+ .get("x-test-header").getValue());
+ }
+
@Test
void testCreateBlobSnapshot() {
final String snapshotId = "2026-04-15T10:00:00.0000000Z";
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/BlobEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/BlobEndpointBuilderFactory.java
index 6501d349e907..2c237fa24797 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/BlobEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/BlobEndpointBuilderFactory.java
@@ -4339,6 +4339,19 @@ public interface BlobEndpointBuilderFactory {
public String azureStorageBlobSnapshotId() {
return "CamelAzureStorageBlobSnapshotId";
}
+ /**
+ * (producer) (setBlobTags) The tags to set on the blob as key-value
+ * pairs. (consumer) The tags retrieved from the blob.
+ *
+ * The option is a: {@code Map<String,String>} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code AzureStorageBlobTags}.
+ */
+ public String azureStorageBlobTags() {
+ return "CamelAzureStorageBlobTags";
+ }
}
static BlobEndpointBuilder endpointBuilder(String componentName, String path) {
class BlobEndpointBuilderImpl extends AbstractEndpointBuilder implements BlobEndpointBuilder, AdvancedBlobEndpointBuilder {