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 291784c71a0d CAMEL-23339: camel-azure-storage-blob - Add support for
immutability policy and legal hold operations (#22826)
291784c71a0d is described below
commit 291784c71a0d76b3bcb0d1035bb23fde41103aa4
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Apr 29 13:50:07 2026 +0200
CAMEL-23339: camel-azure-storage-blob - Add support for immutability policy
and legal hold operations (#22826)
Expose Azure Blob Storage immutability policies and legal holds through
two new producer operations on the camel-azure-storage-blob component.
Both features are required for compliance and quarantine workflows
where blobs must remain unmodified during processing.
- BlobOperationsDefinition: new setBlobLegalHold and
setBlobImmutabilityPolicy
- BlobConstants: new BLOB_LEGAL_HOLD, BLOB_IMMUTABILITY_POLICY,
BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME and BLOB_IMMUTABILITY_POLICY_MODE
headers
- BlobExchangeHeaders: readers and writers for the new headers
- BlobConfigurationOptionsProxy: resolves the new options from headers
- BlobClientWrapper: setLegalHold and setImmutabilityPolicy delegating
to BlobClientBase.setLegalHoldWithResponse /
setImmutabilityPolicyWithResponse
- BlobOperations: setBlobLegalHold reads the flag from the dedicated
header or message body; setBlobImmutabilityPolicy accepts a pre-built
BlobImmutabilityPolicy via body/header or builds one from the expiry
time / mode headers (defaulting to UNLOCKED)
- BlobProducer: dispatches the two new operations
- Unit tests covering header- and body-driven inputs, mode defaulting
and missing-input validation
- Documentation update with operation table entries and usage examples
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
.../catalog/components/azure-storage-blob.json | 8 +-
.../azure/storage/blob/azure-storage-blob.json | 8 +-
.../main/docs/azure-storage-blob-component.adoc | 33 +++++
.../blob/BlobConfigurationOptionsProxy.java | 18 +++
.../azure/storage/blob/BlobConstants.java | 21 ++++
.../azure/storage/blob/BlobExchangeHeaders.java | 33 +++++
.../storage/blob/BlobOperationsDefinition.java | 16 ++-
.../component/azure/storage/blob/BlobProducer.java | 6 +
.../storage/blob/client/BlobClientWrapper.java | 15 +++
.../storage/blob/operations/BlobOperations.java | 82 +++++++++++++
.../blob/operations/BlobOperationsTest.java | 133 +++++++++++++++++++++
.../endpoint/dsl/BlobEndpointBuilderFactory.java | 63 ++++++++++
12 files changed, 431 insertions(+), 5 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 34546924c3df..f9decbae71fa 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
@@ -77,7 +77,7 @@
"sourceBlobAccessKey": { "index": 50, "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, "security": "secret", "configurationClass":
"org.apache.camel.component.azure.storage.blob.BlobConfiguration",
"configurationField": "configuration", "description": "Source Blob Access Key:
for copyblob operation, sadly, [...]
},
"headers": {
- "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType":
"org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition",
"enum": [ "listBlobContainers", "findBlobsByTags", "createBlobContainer",
"deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob",
"downloadBlobToFile", "downloadLink", "uploadBlockBlob",
"uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlock [...]
+ "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType":
"org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition",
"enum": [ "listBlobContainers", "findBlobsByTags", "createBlobContainer",
"deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob",
"downloadBlobToFile", "downloadLink", "uploadBlockBlob",
"uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlock [...]
"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" },
@@ -150,7 +150,11 @@
"CamelAzureStorageBlobVersionId": { "index": 70, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The blob version identifier. On read
operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as
input to target a specific blob version when versioning is enabled on the
storage account. On the consumer side it is [...]
"CamelAzureStorageBlobIsCurrentVersion": { "index": 71, "kind": "header",
"displayName": "", "group": "consumer", "label": "consumer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Flag indicating whether this is the
current version of the blob.", "constantName":
"org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_IS_CURRENT_VERSION"
},
"CamelAzureStorageBlobTags": { "index": 72, "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" },
- "CamelAzureStorageBlobTagFilter": { "index": 73, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(findBlobsByTags) A SQL-like expression
that filters blobs across the storage account based on their index tags, for
example Environment = 'Production' AND Status = 'Active'.", "constantName":
"org.apache.camel.component.a [...]
+ "CamelAzureStorageBlobTagFilter": { "index": 73, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(findBlobsByTags) A SQL-like expression
that filters blobs across the storage account based on their index tags, for
example Environment = 'Production' AND Status = 'Active'.", "constantName":
"org.apache.camel.component.a [...]
+ "CamelAzureStorageBlobLegalHold": { "index": 74, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(producer) (setBlobLegalHold) The legal
hold status to set on the blob. When set to true the blob is protected from
modification and deletion until the hold is cleared by setting the value to
false. (consumer) The legal hold status [...]
+ "CamelAzureStorageBlobImmutabilityPolicy": { "index": 75, "kind":
"header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType":
"com.azure.storage.blob.models.BlobImmutabilityPolicy", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) A pre-built BlobImmutabilityPolicy object that
overrides the policy expiry time and mode headers when present.",
"constantName": "org.apach [...]
+ "CamelAzureStorageBlobImmutabilityPolicyExpiryTime": { "index": 76,
"kind": "header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType": "java.time.OffsetDateTime", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) The expiry time of the time-based retention
policy. Required unless a pre-built BlobImmutabilityPolicy is provided via the
body or the CamelAzureStorageBlobI [...]
+ "CamelAzureStorageBlobImmutabilityPolicyMode": { "index": 77, "kind":
"header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType":
"com.azure.storage.blob.models.BlobImmutabilityPolicyMode", "enum": [
"Mutable", "Unlocked", "Locked" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) The mode of the immutability policy: UNLOCKED
(default, can be modified or deleted), L [...]
},
"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" },
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 34546924c3df..f9decbae71fa 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
@@ -77,7 +77,7 @@
"sourceBlobAccessKey": { "index": 50, "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, "security": "secret", "configurationClass":
"org.apache.camel.component.azure.storage.blob.BlobConfiguration",
"configurationField": "configuration", "description": "Source Blob Access Key:
for copyblob operation, sadly, [...]
},
"headers": {
- "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType":
"org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition",
"enum": [ "listBlobContainers", "findBlobsByTags", "createBlobContainer",
"deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob",
"downloadBlobToFile", "downloadLink", "uploadBlockBlob",
"uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlock [...]
+ "CamelAzureStorageBlobOperation": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType":
"org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition",
"enum": [ "listBlobContainers", "findBlobsByTags", "createBlobContainer",
"deleteBlobContainer", "listBlobs", "getBlob", "deleteBlob",
"downloadBlobToFile", "downloadLink", "uploadBlockBlob",
"uploadBlockBlobChunked", "stageBlockBlobList", "commitBlobBlock [...]
"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" },
@@ -150,7 +150,11 @@
"CamelAzureStorageBlobVersionId": { "index": 70, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The blob version identifier. On read
operations (getBlob, downloadBlobToFile, downloadLink) it can be provided as
input to target a specific blob version when versioning is enabled on the
storage account. On the consumer side it is [...]
"CamelAzureStorageBlobIsCurrentVersion": { "index": 71, "kind": "header",
"displayName": "", "group": "consumer", "label": "consumer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Flag indicating whether this is the
current version of the blob.", "constantName":
"org.apache.camel.component.azure.storage.blob.BlobConstants#BLOB_IS_CURRENT_VERSION"
},
"CamelAzureStorageBlobTags": { "index": 72, "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" },
- "CamelAzureStorageBlobTagFilter": { "index": 73, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(findBlobsByTags) A SQL-like expression
that filters blobs across the storage account based on their index tags, for
example Environment = 'Production' AND Status = 'Active'.", "constantName":
"org.apache.camel.component.a [...]
+ "CamelAzureStorageBlobTagFilter": { "index": 73, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(findBlobsByTags) A SQL-like expression
that filters blobs across the storage account based on their index tags, for
example Environment = 'Production' AND Status = 'Active'.", "constantName":
"org.apache.camel.component.a [...]
+ "CamelAzureStorageBlobLegalHold": { "index": 74, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "(producer) (setBlobLegalHold) The legal
hold status to set on the blob. When set to true the blob is protected from
modification and deletion until the hold is cleared by setting the value to
false. (consumer) The legal hold status [...]
+ "CamelAzureStorageBlobImmutabilityPolicy": { "index": 75, "kind":
"header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType":
"com.azure.storage.blob.models.BlobImmutabilityPolicy", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) A pre-built BlobImmutabilityPolicy object that
overrides the policy expiry time and mode headers when present.",
"constantName": "org.apach [...]
+ "CamelAzureStorageBlobImmutabilityPolicyExpiryTime": { "index": 76,
"kind": "header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType": "java.time.OffsetDateTime", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) The expiry time of the time-based retention
policy. Required unless a pre-built BlobImmutabilityPolicy is provided via the
body or the CamelAzureStorageBlobI [...]
+ "CamelAzureStorageBlobImmutabilityPolicyMode": { "index": 77, "kind":
"header", "displayName": "", "group": "producer", "label": "producer",
"required": false, "javaType":
"com.azure.storage.blob.models.BlobImmutabilityPolicyMode", "enum": [
"Mutable", "Unlocked", "Locked" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description":
"(setBlobImmutabilityPolicy) The mode of the immutability policy: UNLOCKED
(default, can be modified or deleted), L [...]
},
"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" },
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 ca2693ab6950..452b6f1d14b5 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
@@ -168,6 +168,8 @@ and existing blocks together. Any blocks not specified in
the block list and per
|`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.
+|`setBlobLegalHold`|`Common`|Sets a legal hold on a blob. The legal hold flag
(`Boolean`) is provided via the `CamelAzureStorageBlobLegalHold` header or the
message body. While a legal hold is set, the blob cannot be modified or deleted
until the hold is explicitly cleared by calling the operation again with
`false`.
+|`setBlobImmutabilityPolicy`|`Common`|Sets a time-based immutability policy on
a blob. The policy expiry time (`OffsetDateTime`) is read from the
`CamelAzureStorageBlobImmutabilityPolicyExpiryTime` header and the policy mode
(`BlobImmutabilityPolicyMode`, defaults to `UNLOCKED`) from
`CamelAzureStorageBlobImmutabilityPolicyMode`. A pre-built
`BlobImmutabilityPolicy` may also be supplied via the message body or
`CamelAzureStorageBlobImmutabilityPolicy` header.
|===
Refer to the example section in this page to learn how to use these operations
into your camel application.
@@ -793,6 +795,37 @@ from("direct:findBlobsByTags")
.to("mock:result");
--------------------------------------------------------------------------------
+- `setBlobLegalHold`
+
+[source,java]
+--------------------------------------------------------------------------------
+
+// place a legal hold on the blob (e.g. for compliance / quarantine workflows)
+from("direct:setLegalHold")
+ .setHeader(BlobConstants.BLOB_LEGAL_HOLD, constant(true))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client")
+ .to("mock:result");
+
+// clear the legal hold once the workflow is complete
+from("direct:clearLegalHold")
+ .setHeader(BlobConstants.BLOB_LEGAL_HOLD, constant(false))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client")
+ .to("mock:result");
+--------------------------------------------------------------------------------
+
+- `setBlobImmutabilityPolicy`
+
+[source,java]
+--------------------------------------------------------------------------------
+
+// apply a 7-day unlocked time-based retention policy (can be modified or
deleted)
+from("direct:setImmutabilityPolicy")
+ .setHeader(BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME,
constant(OffsetDateTime.now().plusDays(7)))
+ .setHeader(BlobConstants.BLOB_IMMUTABILITY_POLICY_MODE,
constant(BlobImmutabilityPolicyMode.UNLOCKED))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobImmutabilityPolicy&serviceClient=#client")
+ .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/BlobConfigurationOptionsProxy.java
b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/BlobConfigurationOptionsProxy.java
index 427d2715a3ed..b13829fe0b25 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
@@ -25,6 +25,8 @@ import java.util.function.Supplier;
import com.azure.core.util.Context;
import com.azure.storage.blob.models.AccessTier;
import com.azure.storage.blob.models.BlobHttpHeaders;
+import com.azure.storage.blob.models.BlobImmutabilityPolicy;
+import com.azure.storage.blob.models.BlobImmutabilityPolicyMode;
import com.azure.storage.blob.models.BlobListDetails;
import com.azure.storage.blob.models.BlobRange;
import com.azure.storage.blob.models.BlobRequestConditions;
@@ -287,6 +289,22 @@ public class BlobConfigurationOptionsProxy {
return BlobExchangeHeaders.getBlobTagFilterFromHeaders(exchange);
}
+ public Boolean getBlobLegalHold(final Exchange exchange) {
+ return BlobExchangeHeaders.getBlobLegalHoldFromHeaders(exchange);
+ }
+
+ public BlobImmutabilityPolicy getBlobImmutabilityPolicy(final Exchange
exchange) {
+ return
BlobExchangeHeaders.getBlobImmutabilityPolicyFromHeaders(exchange);
+ }
+
+ public OffsetDateTime getBlobImmutabilityPolicyExpiryTime(final Exchange
exchange) {
+ return
BlobExchangeHeaders.getBlobImmutabilityPolicyExpiryTimeFromHeaders(exchange);
+ }
+
+ public BlobImmutabilityPolicyMode getBlobImmutabilityPolicyMode(final
Exchange exchange) {
+ return
BlobExchangeHeaders.getBlobImmutabilityPolicyModeFromHeaders(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 70889bdbc083..47fe9e15f0fe 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
@@ -306,6 +306,27 @@ public final class BlobConstants {
+ " index tags, for example `\"Environment\" =
'Production' AND \"Status\" = 'Active'`.",
javaType = "String")
public static final String BLOB_TAG_FILTER = HEADER_PREFIX + "TagFilter";
+ @Metadata(description = "(producer) (setBlobLegalHold) The legal hold
status to set on the blob. When set to true the blob is"
+ + " protected from modification and deletion until
the hold is cleared by setting the value to false.\n"
+ + "(consumer) The legal hold status returned by
the setBlobLegalHold operation.",
+ javaType = "Boolean")
+ public static final String BLOB_LEGAL_HOLD = HEADER_PREFIX + "LegalHold";
+ @Metadata(label = "producer",
+ description = "(setBlobImmutabilityPolicy) A pre-built
`BlobImmutabilityPolicy` object that overrides the policy"
+ + " expiry time and mode headers when present.",
+ javaType =
"com.azure.storage.blob.models.BlobImmutabilityPolicy")
+ public static final String BLOB_IMMUTABILITY_POLICY = HEADER_PREFIX +
"ImmutabilityPolicy";
+ @Metadata(label = "producer",
+ description = "(setBlobImmutabilityPolicy) The expiry time of
the time-based retention policy. Required unless a"
+ + " pre-built `BlobImmutabilityPolicy` is provided
via the body or the"
+ + " `CamelAzureStorageBlobImmutabilityPolicy`
header.",
+ javaType = "java.time.OffsetDateTime")
+ public static final String BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME =
HEADER_PREFIX + "ImmutabilityPolicyExpiryTime";
+ @Metadata(label = "producer",
+ description = "(setBlobImmutabilityPolicy) The mode of the
immutability policy: `UNLOCKED` (default, can be"
+ + " modified or deleted), `LOCKED` (cannot be
modified or shortened, only extended), or `MUTABLE`.",
+ javaType =
"com.azure.storage.blob.models.BlobImmutabilityPolicyMode")
+ public static final String BLOB_IMMUTABILITY_POLICY_MODE = HEADER_PREFIX +
"ImmutabilityPolicyMode";
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 d851ac8011b2..c1b0abc838eb 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
@@ -28,6 +28,8 @@ import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.ArchiveStatus;
import com.azure.storage.blob.models.BlobDownloadHeaders;
import com.azure.storage.blob.models.BlobHttpHeaders;
+import com.azure.storage.blob.models.BlobImmutabilityPolicy;
+import com.azure.storage.blob.models.BlobImmutabilityPolicyMode;
import com.azure.storage.blob.models.BlobListDetails;
import com.azure.storage.blob.models.BlobProperties;
import com.azure.storage.blob.models.BlobRequestConditions;
@@ -511,8 +513,39 @@ public class BlobExchangeHeaders {
return getObjectFromHeaders(exchange, BlobConstants.BLOB_TAG_FILTER,
String.class);
}
+ public static Boolean getBlobLegalHoldFromHeaders(final Exchange exchange)
{
+ return getObjectFromHeaders(exchange, BlobConstants.BLOB_LEGAL_HOLD,
Boolean.class);
+ }
+
+ public static BlobImmutabilityPolicy
getBlobImmutabilityPolicyFromHeaders(final Exchange exchange) {
+ return getObjectFromHeaders(exchange,
BlobConstants.BLOB_IMMUTABILITY_POLICY, BlobImmutabilityPolicy.class);
+ }
+
+ public static OffsetDateTime
getBlobImmutabilityPolicyExpiryTimeFromHeaders(final Exchange exchange) {
+ return getObjectFromHeaders(exchange,
BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME, OffsetDateTime.class);
+ }
+
+ public static BlobImmutabilityPolicyMode
getBlobImmutabilityPolicyModeFromHeaders(final Exchange exchange) {
+ return getObjectFromHeaders(exchange,
BlobConstants.BLOB_IMMUTABILITY_POLICY_MODE, BlobImmutabilityPolicyMode.class);
+ }
+
public BlobExchangeHeaders blobTags(final Map<String, String> tags) {
headers.put(BlobConstants.BLOB_TAGS, tags);
return this;
}
+
+ public BlobExchangeHeaders blobLegalHold(final Boolean legalHold) {
+ headers.put(BlobConstants.BLOB_LEGAL_HOLD, legalHold);
+ return this;
+ }
+
+ public BlobExchangeHeaders blobImmutabilityPolicyExpiryTime(final
OffsetDateTime expiryTime) {
+ headers.put(BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME,
expiryTime);
+ return this;
+ }
+
+ public BlobExchangeHeaders blobImmutabilityPolicyMode(final
BlobImmutabilityPolicyMode policyMode) {
+ headers.put(BlobConstants.BLOB_IMMUTABILITY_POLICY_MODE, policyMode);
+ 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 c7a3e79ef356..60e953936d5b 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
@@ -155,5 +155,19 @@ public enum BlobOperationsDefinition {
/**
* Retrieves user-defined index tags from a blob.
*/
- getBlobTags
+ getBlobTags,
+ /**
+ * Sets a legal hold on a blob. Legal holds prevent blob modification and
deletion until the hold is explicitly
+ * cleared. The legal hold flag is read from the {@code
CamelAzureStorageBlobLegalHold} header or the message body
+ * (Boolean).
+ */
+ setBlobLegalHold,
+ /**
+ * Sets a time-based immutability policy on a blob. The policy expiry time
is read from the
+ * {@code CamelAzureStorageBlobImmutabilityPolicyExpiryTime} header
(OffsetDateTime) and the policy mode from the
+ * {@code CamelAzureStorageBlobImmutabilityPolicyMode} header
(BlobImmutabilityPolicyMode, defaults to UNLOCKED). A
+ * pre-built {@code BlobImmutabilityPolicy} can also be passed via the
message body or the
+ * {@code CamelAzureStorageBlobImmutabilityPolicy} header.
+ */
+ setBlobImmutabilityPolicy
}
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 0514f5f22ca0..26f6fd65a6a0 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
@@ -141,6 +141,12 @@ public class BlobProducer extends DefaultProducer {
case getBlobTags:
setResponse(exchange,
getBlobOperations(exchange).getBlobTags(exchange));
break;
+ case setBlobLegalHold:
+ setResponse(exchange,
getBlobOperations(exchange).setBlobLegalHold(exchange));
+ break;
+ case setBlobImmutabilityPolicy:
+ setResponse(exchange,
getBlobOperations(exchange).setBlobImmutabilityPolicy(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 cd33adf3a391..94d6541fb636 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
@@ -40,6 +40,8 @@ import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.AppendBlobRequestConditions;
import com.azure.storage.blob.models.BlobDownloadHeaders;
import com.azure.storage.blob.models.BlobHttpHeaders;
+import com.azure.storage.blob.models.BlobImmutabilityPolicy;
+import com.azure.storage.blob.models.BlobLegalHoldResult;
import com.azure.storage.blob.models.BlobProperties;
import com.azure.storage.blob.models.BlobRange;
import com.azure.storage.blob.models.BlobRequestConditions;
@@ -382,6 +384,19 @@ public class BlobClientWrapper {
return client.getTagsWithResponse(options, timeout, Context.NONE);
}
+ public Response<BlobLegalHoldResult> setLegalHold(
+ final boolean legalHold,
+ final Duration timeout) {
+ return client.setLegalHoldWithResponse(legalHold, timeout,
Context.NONE);
+ }
+
+ public Response<BlobImmutabilityPolicy> setImmutabilityPolicy(
+ final BlobImmutabilityPolicy policy,
+ final BlobRequestConditions requestConditions,
+ final Duration timeout) {
+ return client.setImmutabilityPolicyWithResponse(policy,
requestConditions, 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 005045f6176c..eb8367774843 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
@@ -35,6 +35,9 @@ import com.azure.storage.blob.models.AccessTier;
import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.BlobDownloadHeaders;
import com.azure.storage.blob.models.BlobHttpHeaders;
+import com.azure.storage.blob.models.BlobImmutabilityPolicy;
+import com.azure.storage.blob.models.BlobImmutabilityPolicyMode;
+import com.azure.storage.blob.models.BlobLegalHoldResult;
import com.azure.storage.blob.models.BlobProperties;
import com.azure.storage.blob.models.BlobRange;
import com.azure.storage.blob.models.BlobRequestConditions;
@@ -661,6 +664,85 @@ public class BlobOperations {
return BlobOperationResponse.create(tags, exchangeHeaders.toMap());
}
+ public BlobOperationResponse setBlobLegalHold(final Exchange exchange) {
+ ObjectHelper.notNull(exchange, MISSING_EXCHANGE);
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Setting legal hold on blob [{}] from exchange [{}]...",
configurationProxy.getBlobName(exchange),
+ exchange);
+ }
+
+ Boolean legalHold = configurationProxy.getBlobLegalHold(exchange);
+ if (legalHold == null) {
+ legalHold = exchange.getIn().getBody(Boolean.class);
+ }
+ if (legalHold == null) {
+ throw new IllegalArgumentException(
+ "Legal hold flag must be specified either as the message
body (Boolean) or via the "
+ + BlobConstants.BLOB_LEGAL_HOLD
+ " header.");
+ }
+
+ final BlobCommonRequestOptions commonRequestOptions =
getCommonRequestOptions(exchange);
+
+ final Response<BlobLegalHoldResult> response =
client.setLegalHold(legalHold, commonRequestOptions.getTimeout());
+
+ final boolean hasLegalHold = response.getValue() != null &&
response.getValue().hasLegalHold();
+ final BlobExchangeHeaders exchangeHeaders =
BlobExchangeHeaders.create()
+ .blobLegalHold(hasLegalHold)
+ .httpHeaders(response.getHeaders());
+
+ return BlobOperationResponse.create(hasLegalHold,
exchangeHeaders.toMap());
+ }
+
+ public BlobOperationResponse setBlobImmutabilityPolicy(final Exchange
exchange) {
+ ObjectHelper.notNull(exchange, MISSING_EXCHANGE);
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Setting immutability policy on blob [{}] from exchange
[{}]...",
+ configurationProxy.getBlobName(exchange), exchange);
+ }
+
+ BlobImmutabilityPolicy policy =
configurationProxy.getBlobImmutabilityPolicy(exchange);
+ if (policy == null) {
+ final Object body = exchange.getIn().getBody();
+ if (body instanceof BlobImmutabilityPolicy) {
+ policy = (BlobImmutabilityPolicy) body;
+ }
+ }
+ if (policy == null) {
+ final OffsetDateTime expiryTime =
configurationProxy.getBlobImmutabilityPolicyExpiryTime(exchange);
+ if (expiryTime == null) {
+ throw new IllegalArgumentException(
+ "Immutability policy expiry time must be specified via
the "
+ +
BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME
+ + " header, or a pre-built
BlobImmutabilityPolicy must be provided via the message body or "
+ +
BlobConstants.BLOB_IMMUTABILITY_POLICY + " header.");
+ }
+ BlobImmutabilityPolicyMode policyMode =
configurationProxy.getBlobImmutabilityPolicyMode(exchange);
+ if (policyMode == null) {
+ policyMode = BlobImmutabilityPolicyMode.UNLOCKED;
+ }
+ policy = new
BlobImmutabilityPolicy().setExpiryTime(expiryTime).setPolicyMode(policyMode);
+ }
+
+ final BlobCommonRequestOptions commonRequestOptions =
getCommonRequestOptions(exchange);
+
+ final Response<BlobImmutabilityPolicy> response =
client.setImmutabilityPolicy(
+ policy,
+ commonRequestOptions.getBlobRequestConditions(),
+ commonRequestOptions.getTimeout());
+
+ final BlobImmutabilityPolicy result = response.getValue();
+ final BlobExchangeHeaders exchangeHeaders =
BlobExchangeHeaders.create()
+ .httpHeaders(response.getHeaders());
+ if (result != null) {
+
exchangeHeaders.blobImmutabilityPolicyExpiryTime(result.getExpiryTime())
+ .blobImmutabilityPolicyMode(result.getPolicyMode());
+ }
+
+ return BlobOperationResponse.create(result, 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/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 0744ed6e7a09..50e5ec8d0d46 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
@@ -30,6 +30,9 @@ import java.util.Map;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.rest.ResponseBase;
import com.azure.storage.blob.models.BlobDownloadHeaders;
+import com.azure.storage.blob.models.BlobImmutabilityPolicy;
+import com.azure.storage.blob.models.BlobImmutabilityPolicyMode;
+import com.azure.storage.blob.models.BlobLegalHoldResult;
import com.azure.storage.blob.models.BlobProperties;
import com.azure.storage.blob.models.BlockBlobItem;
import com.azure.storage.blob.specialized.BlobClientBase;
@@ -50,6 +53,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -58,6 +62,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -388,6 +393,134 @@ class BlobOperationsTest extends CamelTestSupport {
.get("x-test-header").getValue());
}
+ @Test
+ void testSetBlobLegalHoldFromHeader() {
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header",
"111");
+ final BlobLegalHoldResult legalHoldResult =
mock(BlobLegalHoldResult.class);
+ when(legalHoldResult.hasLegalHold()).thenReturn(true);
+
+ when(client.setLegalHold(eq(true), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders,
legalHoldResult, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setHeader(BlobConstants.BLOB_LEGAL_HOLD, true);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final BlobOperationResponse response =
operations.setBlobLegalHold(exchange);
+
+ assertNotNull(response);
+ assertTrue((boolean) response.getBody());
+ assertEquals(true,
response.getHeaders().get(BlobConstants.BLOB_LEGAL_HOLD));
+ assertEquals("111", ((HttpHeaders)
response.getHeaders().get(BlobConstants.RAW_HTTP_HEADERS))
+ .get("x-test-header").getValue());
+ }
+
+ @Test
+ void testSetBlobLegalHoldFromBody() {
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header",
"222");
+ final BlobLegalHoldResult legalHoldResult =
mock(BlobLegalHoldResult.class);
+ when(legalHoldResult.hasLegalHold()).thenReturn(false);
+
+ when(client.setLegalHold(eq(false), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders,
legalHoldResult, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody(false);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final BlobOperationResponse response =
operations.setBlobLegalHold(exchange);
+
+ assertNotNull(response);
+ assertFalse((boolean) response.getBody());
+ assertEquals(false,
response.getHeaders().get(BlobConstants.BLOB_LEGAL_HOLD));
+ }
+
+ @Test
+ void testSetBlobLegalHoldWithNoFlagThrows() {
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody("not-a-boolean");
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ assertThrows(IllegalArgumentException.class, () ->
operations.setBlobLegalHold(exchange));
+ }
+
+ @Test
+ void testSetBlobImmutabilityPolicyFromHeaders() {
+ final OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(7);
+ final BlobImmutabilityPolicy persistedPolicy
+ = new
BlobImmutabilityPolicy().setExpiryTime(expiryTime).setPolicyMode(BlobImmutabilityPolicyMode.LOCKED);
+ final HttpHeaders httpHeaders = new HttpHeaders().set("x-test-header",
"333");
+
+ when(client.setImmutabilityPolicy(any(), any(), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders,
persistedPolicy, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+
exchange.getIn().setHeader(BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME,
expiryTime);
+
exchange.getIn().setHeader(BlobConstants.BLOB_IMMUTABILITY_POLICY_MODE,
BlobImmutabilityPolicyMode.LOCKED);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final BlobOperationResponse response =
operations.setBlobImmutabilityPolicy(exchange);
+
+ assertNotNull(response);
+ final BlobImmutabilityPolicy result = (BlobImmutabilityPolicy)
response.getBody();
+ assertNotNull(result);
+ assertEquals(expiryTime, result.getExpiryTime());
+ assertEquals(BlobImmutabilityPolicyMode.LOCKED,
result.getPolicyMode());
+ assertEquals(expiryTime,
response.getHeaders().get(BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME));
+ assertEquals(BlobImmutabilityPolicyMode.LOCKED,
+
response.getHeaders().get(BlobConstants.BLOB_IMMUTABILITY_POLICY_MODE));
+ }
+
+ @Test
+ void testSetBlobImmutabilityPolicyDefaultsToUnlocked() {
+ final OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1);
+ final BlobImmutabilityPolicy persistedPolicy
+ = new
BlobImmutabilityPolicy().setExpiryTime(expiryTime).setPolicyMode(BlobImmutabilityPolicyMode.UNLOCKED);
+ final HttpHeaders httpHeaders = new HttpHeaders();
+
+ when(client.setImmutabilityPolicy(any(), any(), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders,
persistedPolicy, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+
exchange.getIn().setHeader(BlobConstants.BLOB_IMMUTABILITY_POLICY_EXPIRY_TIME,
expiryTime);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final BlobOperationResponse response =
operations.setBlobImmutabilityPolicy(exchange);
+
+ assertNotNull(response);
+ assertEquals(BlobImmutabilityPolicyMode.UNLOCKED,
+ ((BlobImmutabilityPolicy) response.getBody()).getPolicyMode());
+ }
+
+ @Test
+ void testSetBlobImmutabilityPolicyFromBody() {
+ final OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(3);
+ final BlobImmutabilityPolicy bodyPolicy
+ = new
BlobImmutabilityPolicy().setExpiryTime(expiryTime).setPolicyMode(BlobImmutabilityPolicyMode.UNLOCKED);
+ final HttpHeaders httpHeaders = new HttpHeaders();
+
+ when(client.setImmutabilityPolicy(eq(bodyPolicy), any(), any()))
+ .thenReturn(new ResponseBase<>(null, 200, httpHeaders,
bodyPolicy, null));
+
+ final Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody(bodyPolicy);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final BlobOperationResponse response =
operations.setBlobImmutabilityPolicy(exchange);
+
+ assertNotNull(response);
+ assertEquals(expiryTime, ((BlobImmutabilityPolicy)
response.getBody()).getExpiryTime());
+ verify(client, times(1)).setImmutabilityPolicy(eq(bodyPolicy), any(),
any());
+ }
+
+ @Test
+ void testSetBlobImmutabilityPolicyWithoutExpiryThrows() {
+ final Exchange exchange = new DefaultExchange(context);
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ assertThrows(IllegalArgumentException.class, () ->
operations.setBlobImmutabilityPolicy(exchange));
+ }
+
@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 ff5f6b0dfd2f..ee315bd99902 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
@@ -4452,6 +4452,69 @@ public interface BlobEndpointBuilderFactory {
public String azureStorageBlobTagFilter() {
return "CamelAzureStorageBlobTagFilter";
}
+ /**
+ * (producer) (setBlobLegalHold) The legal hold status to set on the
+ * blob. When set to true the blob is protected from modification and
+ * deletion until the hold is cleared by setting the value to false.
+ * (consumer) The legal hold status returned by the setBlobLegalHold
+ * operation.
+ *
+ * The option is a: {@code Boolean} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code AzureStorageBlobLegalHold}.
+ */
+ public String azureStorageBlobLegalHold() {
+ return "CamelAzureStorageBlobLegalHold";
+ }
+ /**
+ * (setBlobImmutabilityPolicy) A pre-built BlobImmutabilityPolicy
object
+ * that overrides the policy expiry time and mode headers when present.
+ *
+ * The option is a: {@code
+ * com.azure.storage.blob.models.BlobImmutabilityPolicy} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * AzureStorageBlobImmutabilityPolicy}.
+ */
+ public String azureStorageBlobImmutabilityPolicy() {
+ return "CamelAzureStorageBlobImmutabilityPolicy";
+ }
+ /**
+ * (setBlobImmutabilityPolicy) The expiry time of the time-based
+ * retention policy. Required unless a pre-built BlobImmutabilityPolicy
+ * is provided via the body or the
+ * CamelAzureStorageBlobImmutabilityPolicy header.
+ *
+ * The option is a: {@code java.time.OffsetDateTime} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * AzureStorageBlobImmutabilityPolicyExpiryTime}.
+ */
+ public String azureStorageBlobImmutabilityPolicyExpiryTime() {
+ return "CamelAzureStorageBlobImmutabilityPolicyExpiryTime";
+ }
+ /**
+ * (setBlobImmutabilityPolicy) The mode of the immutability policy:
+ * UNLOCKED (default, can be modified or deleted), LOCKED (cannot be
+ * modified or shortened, only extended), or MUTABLE.
+ *
+ * The option is a: {@code
+ * com.azure.storage.blob.models.BlobImmutabilityPolicyMode} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * AzureStorageBlobImmutabilityPolicyMode}.
+ */
+ public String azureStorageBlobImmutabilityPolicyMode() {
+ return "CamelAzureStorageBlobImmutabilityPolicyMode";
+ }
}
static BlobEndpointBuilder endpointBuilder(String componentName, String
path) {
class BlobEndpointBuilderImpl extends AbstractEndpointBuilder
implements BlobEndpointBuilder, AdvancedBlobEndpointBuilder {