This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot-examples.git
The following commit(s) were added to refs/heads/main by this push:
new 5f9f5719 CSB-8935: Add blob index tags routes and test (#218)
5f9f5719 is described below
commit 5f9f5719fa94ed57159cb69a8517b0ee9948fce5
Author: Thomas Diesler <[email protected]>
AuthorDate: Tue Oct 6 10:47:17 2026 +0200
CSB-8935: Add blob index tags routes and test (#218)
* CSB-8935: Add blob index tags routes and test
- Introduces the camel-example-spring-boot-azure-storage-blob module with
basic blob upload/download
- Adds setBlobTags, getBlobTags and findBlobsByTags routes
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CSB-8938: Add blob snapshot routes and test
- Adds createSnapshot route for snapshot creation/reading blob content
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
README.adoc | 4 +-
.../README.adoc | 37 +++++
.../pom.xml | 102 ++++++++++++++
.../main/java/camel/sample/BlobConfiguration.java | 44 ++++++
.../main/java/camel/sample/BlobRouteBuilder.java | 38 +++++
.../camel/sample/BlobSnapshotRouteBuilder.java | 32 +++++
.../java/camel/sample/BlobStorageApplication.java | 28 ++++
.../java/camel/sample/BlobTagsRouteBuilder.java | 42 ++++++
.../src/main/resources/application.yml | 29 ++++
.../src/test/java/camel/sample/BlobSmokeTest.java | 134 ++++++++++++++++++
.../test/java/camel/sample/BlobSnapshotTest.java | 156 +++++++++++++++++++++
.../src/test/java/camel/sample/BlobTagsTest.java | 139 ++++++++++++++++++
azure/pom.xml | 1 +
13 files changed, 785 insertions(+), 1 deletion(-)
diff --git a/README.adoc b/README.adoc
index b59988ad..9aada038 100644
--- a/README.adoc
+++ b/README.adoc
@@ -27,7 +27,7 @@ readme's instructions.
=== Examples
// examples: START
-Number of Examples: 72 (0 deprecated)
+Number of Examples: 73 (0 deprecated)
[width="100%",cols="4,2,4",options="header"]
|===
@@ -81,6 +81,8 @@ Number of Examples: 72 (0 deprecated)
| link:azure/camel-example-spring-boot-azure-servicebus/README.adoc[Spring
Boot Azure Servicebus] (camel-example-spring-boot-azure-servicebus) | Cloud |
An example showing how to work with Camel, Azure Service Bus and Spring Boot
+| link:azure/camel-example-spring-boot-azure-storage-blob/README.adoc[Spring
Boot Azure Storage Blob] (camel-example-spring-boot-azure-storage-blob) | Cloud
| An example showing how to work with Camel, Azure Storage Blob and Spring Boot
+
| link:infinispan/README.adoc[Infinispan] (infinispan) | Cloud | An example
showing the Camel Infinispan component with Spring Boot
| link:webhook/readme.adoc[Webhook] (webhook) | Cloud | Example on how to use
the Camel Webhook component
diff --git a/azure/camel-example-spring-boot-azure-storage-blob/README.adoc
b/azure/camel-example-spring-boot-azure-storage-blob/README.adoc
new file mode 100644
index 00000000..02a401ba
--- /dev/null
+++ b/azure/camel-example-spring-boot-azure-storage-blob/README.adoc
@@ -0,0 +1,37 @@
+== Camel Azure Storage Blob Example
+
+=== Introduction
+
+This example demonstrates how to use the Apache Camel Azure Storage Blob
component with Spring Boot to:
+
+* Upload block blobs
+* Set and get blob index tags
+* Find blobs by tag filter expressions
+* Create and read blob snapshots
+
+=== Running Tests (Azurite)
+
+Tests run automatically against https://github.com/Azure/Azurite[Azurite]
(Azure Storage emulator) via Testcontainers. Docker must be running.
+
+ mvn verify
+
+=== Running with Azure
+
+To run against a real Azure Storage account:
+
+1. Create a General-purpose v2 Storage Account in the Azure portal
+2. Enable blob versioning under Data management > Data protection (optional,
for version operations)
+3. Get your account name and access key from Security + networking > Access
keys
+
+ export AZURE_STORAGE_ACCOUNT_NAME=youraccount
+ export AZURE_STORAGE_ACCESS_KEY=yourkey
+ export AZURE_STORAGE_CONTAINER_NAME=yourcontainer
+ mvn spring-boot:run
+
+=== Help and Contributions
+
+If you hit any problems please let us know on the
https://camel.apache.org/community/mailing-list/[Camel Forums].
+
+Please help us make Apache Camel better - we appreciate any feedback you may
have.
+
+https://camel.apache.org/community/contributing/[Contributing]
diff --git a/azure/camel-example-spring-boot-azure-storage-blob/pom.xml
b/azure/camel-example-spring-boot-azure-storage-blob/pom.xml
new file mode 100644
index 00000000..915f9f56
--- /dev/null
+++ b/azure/camel-example-spring-boot-azure-storage-blob/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.camel.springboot.example</groupId>
+ <artifactId>camel-example-spring-boot-azure</artifactId>
+ <version>4.23.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-example-spring-boot-azure-storage-blob</artifactId>
+ <name>Camel SB Examples :: Azure Storage Blob</name>
+ <description>An example showing how to work with Camel, Azure Storage Blob
and Spring Boot</description>
+
+ <properties>
+ <category>Cloud</category>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ </dependency>
+
+ <!-- Camel -->
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-azure-storage-blob-starter</artifactId>
+ </dependency>
+
+ <!-- Test -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-spring-junit6</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-testcontainers</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <version>${testcontainers-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers-junit-jupiter</artifactId>
+ <version>${testcontainers-version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot-version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobConfiguration.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobConfiguration.java
new file mode 100644
index 00000000..db96c378
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobConfiguration.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import com.azure.storage.blob.BlobServiceClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class BlobConfiguration {
+
+ @Value("${azure.storage.account-name}")
+ private String accountName;
+
+ @Value("${azure.storage.access-key}")
+ private String accessKey;
+
+ @Bean
+ BlobServiceClient serviceClient() {
+ String endpoint = String.format("https://%s.blob.core.windows.net",
accountName);
+ return new BlobServiceClientBuilder()
+ .endpoint(endpoint)
+ .credential(new StorageSharedKeyCredential(accountName,
accessKey))
+ .buildClient();
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobRouteBuilder.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobRouteBuilder.java
new file mode 100644
index 00000000..34c2e638
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobRouteBuilder.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class BlobRouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:uploadBlob")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=uploadBlockBlob"
+ + "&serviceClient=#serviceClient");
+
+ from("direct:getBlob")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=getBlob"
+ + "&serviceClient=#serviceClient")
+ .convertBodyTo(byte[].class);
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobSnapshotRouteBuilder.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobSnapshotRouteBuilder.java
new file mode 100644
index 00000000..7dd4b7d2
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobSnapshotRouteBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class BlobSnapshotRouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:createSnapshot")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=createBlobSnapshot"
+ + "&serviceClient=#serviceClient");
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobStorageApplication.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobStorageApplication.java
new file mode 100644
index 00000000..b23df7aa
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobStorageApplication.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BlobStorageApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BlobStorageApplication.class, args);
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobTagsRouteBuilder.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobTagsRouteBuilder.java
new file mode 100644
index 00000000..a55fc40e
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/java/camel/sample/BlobTagsRouteBuilder.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class BlobTagsRouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:setBlobTags")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=setBlobTags"
+ + "&serviceClient=#serviceClient");
+
+ from("direct:getBlobTags")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=getBlobTags"
+ + "&serviceClient=#serviceClient");
+
+ from("direct:findBlobsByTags")
+
.to("azure-storage-blob://{{azure.storage.account-name}}/{{azure.storage.container-name}}"
+ + "?operation=findBlobsByTags"
+ + "&serviceClient=#serviceClient");
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/main/resources/application.yml
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/resources/application.yml
new file mode 100644
index 00000000..27d640bd
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/main/resources/application.yml
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+azure:
+ storage:
+ account-name: ${AZURE_STORAGE_ACCOUNT_NAME:devstoreaccount1}
+ access-key: ${AZURE_STORAGE_ACCESS_KEY}
+ container-name: ${AZURE_STORAGE_CONTAINER_NAME:testcontainer}
+
+camel:
+ component:
+ azure-storage-blob:
+ credential-type: SHARED_KEY_CREDENTIAL
+ springboot:
+ main-run-controller: true
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSmokeTest.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSmokeTest.java
new file mode 100644
index 00000000..dd835c8e
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSmokeTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import java.io.ByteArrayInputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import com.azure.storage.blob.BlobServiceClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.azure.storage.blob.BlobConstants;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@DirtiesContext
+@CamelSpringBootTest
+@SpringBootTest(classes = {
+ CamelAutoConfiguration.class,
+ BlobSmokeTest.class,
+ BlobSmokeTest.TestConfiguration.class,
+ BlobRouteBuilder.class
+})
+public class BlobSmokeTest {
+
+ static final String ACCOUNT_NAME = "devstoreaccount1";
+ static final String ACCOUNT_KEY =
+
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+ static final String CONTAINER_NAME = "smoketest";
+
+ static GenericContainer<?> azurite = new
GenericContainer<>("mcr.microsoft.com/azure-storage/azurite:3.35.0")
+ .withExposedPorts(10000)
+ .waitingFor(Wait.forListeningPort());
+
+ static {
+ azurite.start();
+ }
+
+ @DynamicPropertySource
+ static void azuriteProperties(DynamicPropertyRegistry registry) {
+ registry.add("azure.storage.account-name", () -> ACCOUNT_NAME);
+ registry.add("azure.storage.container-name", () -> CONTAINER_NAME);
+ registry.add("camel.component.azure-storage-blob.credential-type", ()
-> "SHARED_KEY_CREDENTIAL");
+ }
+
+ @Autowired
+ CamelContext context;
+
+ @Autowired
+ ProducerTemplate template;
+
+ @Test
+ void testAzuriteReachable() throws Exception {
+ String url = String.format("http://%s:%d/devstoreaccount1?comp=list",
+ azurite.getHost(), azurite.getMappedPort(10000));
+ HttpURLConnection conn = (HttpURLConnection)
URI.create(url).toURL().openConnection();
+ conn.setRequestMethod("GET");
+ int status = conn.getResponseCode();
+ assertTrue(status == 200 || status == 403, "Azurite should be
reachable, got " + status);
+ }
+
+ @Test
+ void testServiceClientInRegistry() {
+ BlobServiceClient client =
context.getRegistry().lookupByNameAndType("serviceClient",
BlobServiceClient.class);
+ assertNotNull(client, "serviceClient should be in the Camel registry");
+ }
+
+ @Test
+ void testUploadAndDownloadBlob() {
+ BlobServiceClient client =
context.getRegistry().lookupByNameAndType("serviceClient",
BlobServiceClient.class);
+ client.createBlobContainerIfNotExists(CONTAINER_NAME);
+
+ String expected = "hello azurite";
+ template.send("direct:uploadBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, "smoke.txt");
+ exchange.getIn().setBody(new
ByteArrayInputStream(expected.getBytes(StandardCharsets.UTF_8)));
+ });
+
+ Exchange result = template.send("direct:getBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, "smoke.txt");
+ });
+ assertNull(result.getException(), "Download should succeed: " +
result.getException());
+ assertEquals(expected, result.getMessage().getBody(String.class));
+ }
+
+ @Configuration
+ public static class TestConfiguration {
+ @Bean
+ BlobServiceClient serviceClient() {
+ String endpoint = String.format("http://%s:%d/%s",
+ azurite.getHost(), azurite.getMappedPort(10000),
ACCOUNT_NAME);
+ return new BlobServiceClientBuilder()
+ .endpoint(endpoint)
+ .credential(new StorageSharedKeyCredential(ACCOUNT_NAME,
ACCOUNT_KEY))
+ .buildClient();
+ }
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSnapshotTest.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSnapshotTest.java
new file mode 100644
index 00000000..dc6efdf9
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobSnapshotTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import com.azure.storage.blob.BlobServiceClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.azure.storage.blob.BlobConstants;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@DirtiesContext
+@CamelSpringBootTest
+@SpringBootTest(classes = {
+ CamelAutoConfiguration.class,
+ BlobSnapshotTest.class,
+ BlobSnapshotTest.AzuriteConfiguration.class,
+ BlobRouteBuilder.class,
+ BlobSnapshotRouteBuilder.class
+})
+public class BlobSnapshotTest {
+
+ static final String ACCOUNT_NAME = "devstoreaccount1";
+ static final String ACCOUNT_KEY =
+
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+ static final String CONTAINER_NAME = "snapshottest";
+ static final String BLOB_NAME = "snapshot-blob.txt";
+ static final String ORIGINAL_CONTENT = "original snapshot content";
+
+ static GenericContainer<?> azurite = new
GenericContainer<>("mcr.microsoft.com/azure-storage/azurite:3.35.0")
+ .withExposedPorts(10000)
+ .waitingFor(Wait.forListeningPort());
+
+ static {
+ azurite.start();
+ }
+
+ @DynamicPropertySource
+ static void azuriteProperties(DynamicPropertyRegistry registry) {
+ registry.add("azure.storage.account-name", () -> ACCOUNT_NAME);
+ registry.add("azure.storage.container-name", () -> CONTAINER_NAME);
+ registry.add("camel.component.azure-storage-blob.credential-type", ()
-> "SHARED_KEY_CREDENTIAL");
+ }
+
+ @Configuration
+ static class AzuriteConfiguration {
+ @Bean
+ BlobServiceClient serviceClient() {
+ String endpoint = String.format("http://%s:%d/%s",
+ azurite.getHost(), azurite.getMappedPort(10000),
ACCOUNT_NAME);
+ return new BlobServiceClientBuilder()
+ .endpoint(endpoint)
+ .credential(new StorageSharedKeyCredential(ACCOUNT_NAME,
ACCOUNT_KEY))
+ .buildClient();
+ }
+ }
+
+ @Autowired
+ CamelContext camelContext;
+
+ @Autowired
+ ProducerTemplate template;
+
+ void uploadTestBlob() {
+ BlobServiceClient client =
camelContext.getRegistry().lookupByNameAndType("serviceClient",
BlobServiceClient.class);
+ client.createBlobContainerIfNotExists(CONTAINER_NAME);
+
+ byte[] content = ORIGINAL_CONTENT.getBytes(StandardCharsets.UTF_8);
+ template.send("direct:uploadBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ exchange.getIn().setBody(new ByteArrayInputStream(content));
+ });
+ }
+
+ @Test
+ void testCreateSnapshotReturnsId() {
+ uploadTestBlob();
+
+ Exchange result = template.send("direct:createSnapshot", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ });
+ assertNull(result.getException(), "createSnapshot should succeed: " +
result.getException());
+
+ String snapshotId =
result.getMessage().getHeader(BlobConstants.BLOB_SNAPSHOT_ID, String.class);
+ assertNotNull(snapshotId, "Snapshot ID should be returned in header");
+ }
+
+ @Test
+ void testReadBlobViaSnapshotId() {
+ uploadTestBlob();
+
+ Exchange snapshotResult = template.send("direct:createSnapshot",
exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ });
+ String snapshotId =
snapshotResult.getMessage().getHeader(BlobConstants.BLOB_SNAPSHOT_ID,
String.class);
+ assertNotNull(snapshotId);
+
+ String updatedContent = "modified after snapshot";
+ template.send("direct:uploadBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ exchange.getIn().setBody(new
ByteArrayInputStream(updatedContent.getBytes(StandardCharsets.UTF_8)));
+ });
+
+ Exchange snapshotRead = template.send("direct:getBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ exchange.getIn().setHeader(BlobConstants.BLOB_SNAPSHOT_ID,
snapshotId);
+ });
+ assertNull(snapshotRead.getException(), "Snapshot read should succeed:
" + snapshotRead.getException());
+ String retrieved = snapshotRead.getMessage().getBody(String.class);
+ assertEquals(ORIGINAL_CONTENT, retrieved, "Snapshot should return
original content");
+
+ Exchange liveRead = template.send("direct:getBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ });
+ assertNull(liveRead.getException(), "Live read should succeed: " +
liveRead.getException());
+ String liveRetrieved = liveRead.getMessage().getBody(String.class);
+ assertEquals(updatedContent, liveRetrieved, "Live blob should return
updated content");
+ }
+}
diff --git
a/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobTagsTest.java
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobTagsTest.java
new file mode 100644
index 00000000..4a814249
--- /dev/null
+++
b/azure/camel-example-spring-boot-azure-storage-blob/src/test/java/camel/sample/BlobTagsTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package camel.sample;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import com.azure.storage.blob.BlobServiceClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.azure.storage.blob.BlobConstants;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@DirtiesContext
+@CamelSpringBootTest
+@SpringBootTest(classes = {
+ CamelAutoConfiguration.class,
+ BlobTagsTest.class,
+ BlobTagsTest.AzuriteConfiguration.class,
+ BlobRouteBuilder.class,
+ BlobTagsRouteBuilder.class
+})
+public class BlobTagsTest {
+
+ static final String ACCOUNT_NAME = "devstoreaccount1";
+ static final String ACCOUNT_KEY =
+
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+ static final String CONTAINER_NAME = "tagtest";
+ static final String BLOB_NAME = "test-blob.txt";
+
+ static GenericContainer<?> azurite = new
GenericContainer<>("mcr.microsoft.com/azure-storage/azurite:3.35.0")
+ .withExposedPorts(10000)
+ .waitingFor(Wait.forListeningPort());
+
+ static {
+ azurite.start();
+ }
+
+ @DynamicPropertySource
+ static void azuriteProperties(DynamicPropertyRegistry registry) {
+ registry.add("azure.storage.account-name", () -> ACCOUNT_NAME);
+ registry.add("azure.storage.container-name", () -> CONTAINER_NAME);
+ registry.add("camel.component.azure-storage-blob.credential-type", ()
-> "SHARED_KEY_CREDENTIAL");
+ }
+
+ @Configuration
+ static class AzuriteConfiguration {
+ @Bean
+ BlobServiceClient serviceClient() {
+ String endpoint = String.format("http://%s:%d/%s",
+ azurite.getHost(), azurite.getMappedPort(10000),
ACCOUNT_NAME);
+ return new BlobServiceClientBuilder()
+ .endpoint(endpoint)
+ .credential(new StorageSharedKeyCredential(ACCOUNT_NAME,
ACCOUNT_KEY))
+ .buildClient();
+ }
+ }
+
+ @Autowired
+ CamelContext camelContext;
+
+ @Autowired
+ ProducerTemplate template;
+
+ void uploadTestBlob() {
+ BlobServiceClient client =
camelContext.getRegistry().lookupByNameAndType("serviceClient",
BlobServiceClient.class);
+ client.createBlobContainerIfNotExists(CONTAINER_NAME);
+
+ byte[] content = "hello blob tags".getBytes(StandardCharsets.UTF_8);
+ template.send("direct:uploadBlob", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ exchange.getIn().setBody(new ByteArrayInputStream(content));
+ });
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testSetAndGetBlobTags() {
+ uploadTestBlob();
+
+ Map<String, String> tags = Map.of(
+ "status", "quarantine",
+ "category", "document",
+ "priority", "high");
+
+ Exchange setResult = template.send("direct:setBlobTags", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ exchange.getIn().setHeader(BlobConstants.BLOB_TAGS, tags);
+ });
+ assertNull(setResult.getException(), "setBlobTags should succeed: " +
setResult.getException());
+
+ Exchange result = template.send("direct:getBlobTags", exchange -> {
+ exchange.getIn().setHeader(BlobConstants.BLOB_NAME, BLOB_NAME);
+ });
+ assertNull(result.getException(), "getBlobTags should succeed: " +
result.getException());
+
+ Map<String, String> retrieved = result.getMessage().getBody(Map.class);
+ assertNotNull(retrieved, "Tags should not be null");
+ assertEquals("quarantine", retrieved.get("status"));
+ assertEquals("document", retrieved.get("category"));
+ assertEquals("high", retrieved.get("priority"));
+ }
+}
diff --git a/azure/pom.xml b/azure/pom.xml
index f03b0c4d..b88ba661 100644
--- a/azure/pom.xml
+++ b/azure/pom.xml
@@ -63,6 +63,7 @@
<modules>
<module>camel-example-spring-boot-azure-servicebus</module>
<module>camel-example-spring-boot-azure-eventhubs</module>
+ <module>camel-example-spring-boot-azure-storage-blob</module>
</modules>
</project>