diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index e4491e4fde64..1bd7b614738a 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -15,6 +15,7 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. #### Bugs Fixed - Fixed JDBC/Azure Database and Redis passwordless connection scope defaulting using the wrong `azure.scopes` value for Azure China and Azure US Government when `spring.cloud.azure.profile.cloud-type` is set to `azure_china` or `azure_us_government`. The scopes are now correctly derived from the merged cloud type. ([#47096](https://github.com/Azure/azure-sdk-for-java/issues/47096)) +- Fixed Service Bus autoconfiguration for dedicated producer, consumer, and processor connection details so applications can initialize with only sub-level Service Bus `namespace` or `connection-string` settings and no top-level Service Bus connection configuration. ([#49257](https://github.com/Azure/azure-sdk-for-java/pull/49257)) ### Spring Cloud Azure Stream Binder Service Bus This section includes changes in `spring-cloud-azure-stream-binder-servicebus` module. diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusClientBuilderConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusClientBuilderConfiguration.java index 166d6a6593d4..ff9b8eb98a33 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusClientBuilderConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusClientBuilderConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.util.StringUtils; @Configuration(proxyBeanMethods = false) @Import(AzureServiceBusPropertiesConfiguration.class) @@ -50,7 +51,14 @@ ServiceBusClientBuilderFactory serviceBusClientBuilderFactory( @Bean @ConditionalOnMissingBean - ServiceBusClientBuilder serviceBusClientBuilder(ServiceBusClientBuilderFactory factory) { + ServiceBusClientBuilder serviceBusClientBuilder( + ServiceBusClientBuilderFactory factory, + ObjectProvider> connectionStringProviders) { + if (!StringUtils.hasText(this.serviceBusProperties.getConnectionString()) + && !StringUtils.hasText(this.serviceBusProperties.getNamespace()) + && connectionStringProviders.orderedStream().findFirst().isEmpty()) { + return new ServiceBusClientBuilder(); + } return factory.build(); } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/properties/ConfigurationWithoutConnectionDetailsBean.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/properties/ConfigurationWithoutConnectionDetailsBean.java index 238dcde601e5..42b320140b4a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/properties/ConfigurationWithoutConnectionDetailsBean.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/properties/ConfigurationWithoutConnectionDetailsBean.java @@ -13,7 +13,11 @@ @ConditionalOnMissingBean(type = "com.azure.spring.cloud.autoconfigure.implementation.servicebus.properties.AzureServiceBusConnectionDetails") @ConditionalOnProperty(value = "spring.cloud.azure.servicebus.enabled", havingValue = "true", matchIfMissing = true) -@ConditionalOnAnyProperty(prefix = "spring.cloud.azure.servicebus", name = {"connection-string", "namespace"}) +@ConditionalOnAnyProperty(prefix = "spring.cloud.azure.servicebus", + name = {"connection-string", "namespace", + "producer.connection-string", "producer.namespace", + "consumer.connection-string", "consumer.namespace", + "processor.connection-string", "processor.namespace"}) class ConfigurationWithoutConnectionDetailsBean { private final AzureGlobalProperties azureGlobalProperties; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusAutoConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusAutoConfigurationTests.java index 63195f6cd724..1c9265e06b42 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusAutoConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/AzureServiceBusAutoConfigurationTests.java @@ -5,6 +5,9 @@ import com.azure.core.amqp.AmqpTransportType; import com.azure.messaging.servicebus.ServiceBusClientBuilder; +import com.azure.messaging.servicebus.ServiceBusProcessorClient; +import com.azure.messaging.servicebus.ServiceBusReceiverClient; +import com.azure.messaging.servicebus.ServiceBusSenderClient; import com.azure.messaging.servicebus.models.ServiceBusReceiveMode; import com.azure.spring.cloud.autoconfigure.implementation.AbstractAzureServiceConfigurationTests; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; @@ -158,6 +161,100 @@ void configureRetryShouldApply() { }); } + @Test + void producerDedicatedConnectionStringShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.producer.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "producer-namespace"), + "spring.cloud.azure.servicebus.producer.entity-name=test-queue", + "spring.cloud.azure.servicebus.producer.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusSenderClient.class); + }); + } + + @Test + void producerDedicatedNamespaceShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.producer.namespace=producer-namespace", + "spring.cloud.azure.servicebus.producer.entity-name=test-queue", + "spring.cloud.azure.servicebus.producer.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusSenderClient.class); + }); + } + + @Test + void consumerDedicatedConnectionStringShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.consumer.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "consumer-namespace"), + "spring.cloud.azure.servicebus.consumer.entity-name=test-queue", + "spring.cloud.azure.servicebus.consumer.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusReceiverClient.class); + }); + } + + @Test + void consumerDedicatedNamespaceShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.consumer.namespace=consumer-namespace", + "spring.cloud.azure.servicebus.consumer.entity-name=test-queue", + "spring.cloud.azure.servicebus.consumer.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusReceiverClient.class); + }); + } + + @Test + void processorDedicatedConnectionStringShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.processor.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "processor-namespace"), + "spring.cloud.azure.servicebus.processor.entity-name=test-queue", + "spring.cloud.azure.servicebus.processor.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .withBean(ServiceBusRecordMessageListener.class, () -> messageContext -> { }) + .withBean(ServiceBusErrorHandler.class, () -> errorContext -> { }) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusProcessorClient.class); + }); + } + + @Test + void processorDedicatedNamespaceShouldConfigureWithoutTopLevelConnectionInfo() { + this.contextRunner + .withPropertyValues( + "spring.cloud.azure.servicebus.processor.namespace=processor-namespace", + "spring.cloud.azure.servicebus.processor.entity-name=test-queue", + "spring.cloud.azure.servicebus.processor.entity-type=queue" + ) + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .withBean(ServiceBusRecordMessageListener.class, () -> messageContext -> { }) + .withBean(ServiceBusErrorHandler.class, () -> errorContext -> { }) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusProperties.class); + assertThat(context).hasSingleBean(ServiceBusProcessorClient.class); + }); + } + @Test void configurationPropertiesShouldBind() { String connectionString = String.format(CONNECTION_STRING_FORMAT, "fake-namespace");