Skip to content

Commit b68fb6e

Browse files
committed
[Spring] Support custom ServiceBus JMS factory creation
1 parent 29799d2 commit b68fb6e

6 files changed

Lines changed: 169 additions & 42 deletions

File tree

sdk/spring/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module.
88

99
#### Features Added
1010

11+
- Added `AzureServiceBusJmsConnectionFactoryFactory` to allow applications to customize how `ServiceBusJmsConnectionFactory` instances are created, including support for custom subclasses.
12+
1113
#### Breaking Changes
1214

1315
#### Bugs Fixed

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33

44
package com.azure.spring.cloud.autoconfigure.implementation.jms;
55

6+
import com.azure.core.credential.TokenCredential;
7+
import com.azure.identity.extensions.implementation.credential.TokenCredentialProviderOptions;
8+
import com.azure.identity.extensions.implementation.credential.provider.TokenCredentialProvider;
69
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
10+
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;
711
import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
812
import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties;
913
import com.azure.spring.cloud.autoconfigure.implementation.resourcemanager.AzureServiceBusResourceManagerAutoConfiguration;
14+
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryFactory;
1015
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryCustomizer;
1116
import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils;
1217
import com.azure.spring.cloud.core.implementation.util.ReflectionUtils;
@@ -19,6 +24,7 @@
1924
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2025
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2126
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2228
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2329
import org.springframework.boot.jms.autoconfigure.JmsAutoConfiguration;
2430
import org.springframework.boot.jms.autoconfigure.JndiConnectionFactoryAutoConfiguration;
@@ -31,8 +37,11 @@
3137
import java.net.URI;
3238
import java.util.EnumMap;
3339
import java.util.Map;
40+
import java.util.Properties;
3441
import java.util.function.BiFunction;
3542

43+
import static com.azure.spring.cloud.autoconfigure.implementation.util.SpringPasswordlessPropertiesUtils.enhancePasswordlessProperties;
44+
3645
/**
3746
* {@link EnableAutoConfiguration Auto-configuration} for Azure Service Bus JMS support.
3847
*
@@ -59,6 +68,24 @@ AzureServiceBusJmsProperties serviceBusJmsProperties(AzureGlobalProperties azure
5968
return mergeAzureProperties(azureGlobalProperties, properties);
6069
}
6170

71+
@Bean
72+
@ConditionalOnMissingBean
73+
AzureServiceBusJmsConnectionFactoryFactory azureServiceBusJmsConnectionFactoryFactory(final AzureServiceBusJmsProperties properties) {
74+
return () -> {
75+
if (properties.isPasswordlessEnabled()) {
76+
String hostName =
77+
properties.getNamespace() + "." + properties.getProfile().getEnvironment().getServiceBusDomainName();
78+
Properties passwordlessProperties = properties.toPasswordlessProperties();
79+
enhancePasswordlessProperties(AzureServiceBusJmsProperties.PREFIX, properties, passwordlessProperties);
80+
TokenCredentialProvider tokenCredentialProvider = TokenCredentialProvider.createDefault(new TokenCredentialProviderOptions(passwordlessProperties));
81+
TokenCredential tokenCredential = tokenCredentialProvider.get();
82+
return new ServiceBusJmsConnectionFactory(tokenCredential, hostName, new ServiceBusJmsConnectionFactorySettings());
83+
} else {
84+
return new ServiceBusJmsConnectionFactory(properties.getConnectionString(), new ServiceBusJmsConnectionFactorySettings());
85+
}
86+
};
87+
}
88+
6289
/**
6390
* Standard tier does not support the property "com.microsoft:is-client-provider", so remove it.
6491
*/

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33

44
package com.azure.spring.cloud.autoconfigure.implementation.jms;
55

6+
import com.azure.core.credential.TokenCredential;
7+
import com.azure.identity.extensions.implementation.credential.TokenCredentialProviderOptions;
8+
import com.azure.identity.extensions.implementation.credential.provider.TokenCredentialProvider;
69
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
10+
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;
711
import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties;
812
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryCustomizer;
13+
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryFactory;
914
import jakarta.jms.ConnectionFactory;
1015
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
1116
import org.springframework.beans.BeansException;
@@ -26,8 +31,10 @@
2631
import org.springframework.jms.connection.CachingConnectionFactory;
2732
import org.springframework.util.ClassUtils;
2833

34+
import java.util.Properties;
2935
import java.util.stream.Collectors;
3036

37+
import static com.azure.spring.cloud.autoconfigure.implementation.util.SpringPasswordlessPropertiesUtils.enhancePasswordlessProperties;
3138
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
3239

3340
/**
@@ -56,7 +63,19 @@ static ServiceBusJmsConnectionFactory createServiceBusJmsConnectionFactory(
5663
AzureServiceBusJmsProperties properties,
5764
java.util.List<AzureServiceBusJmsConnectionFactoryCustomizer> customizers) {
5865
return new ServiceBusJmsConnectionFactoryFactory(properties, customizers)
59-
.createConnectionFactory(ServiceBusJmsConnectionFactory.class);
66+
.createConnectionFactory(() -> {
67+
if (properties.isPasswordlessEnabled()) {
68+
String hostName =
69+
properties.getNamespace() + "." + properties.getProfile().getEnvironment().getServiceBusDomainName();
70+
Properties passwordlessProperties = properties.toPasswordlessProperties();
71+
enhancePasswordlessProperties(AzureServiceBusJmsProperties.PREFIX, properties, passwordlessProperties);
72+
TokenCredentialProvider tokenCredentialProvider = TokenCredentialProvider.createDefault(new TokenCredentialProviderOptions(passwordlessProperties));
73+
TokenCredential tokenCredential = tokenCredentialProvider.get();
74+
return new ServiceBusJmsConnectionFactory(tokenCredential, hostName, new ServiceBusJmsConnectionFactorySettings());
75+
} else {
76+
return new ServiceBusJmsConnectionFactory(properties.getConnectionString(), new ServiceBusJmsConnectionFactorySettings());
77+
}
78+
});
6079
}
6180

6281
/**
@@ -195,10 +214,11 @@ private void registerJmsPoolConnectionFactory(BeanDefinitionRegistry registry) {
195214

196215
private ServiceBusJmsConnectionFactory createServiceBusJmsConnectionFactory() {
197216
AzureServiceBusJmsProperties serviceBusJmsProperties = beanFactory.getBean(AzureServiceBusJmsProperties.class);
217+
AzureServiceBusJmsConnectionFactoryFactory instanceFactory = beanFactory.getBean(AzureServiceBusJmsConnectionFactoryFactory.class);
198218
ObjectProvider<AzureServiceBusJmsConnectionFactoryCustomizer> factoryCustomizers = beanFactory.getBeanProvider(AzureServiceBusJmsConnectionFactoryCustomizer.class);
199-
return ServiceBusJmsConnectionFactoryConfiguration.createServiceBusJmsConnectionFactory(
200-
serviceBusJmsProperties,
201-
factoryCustomizers.orderedStream().collect(Collectors.toList()));
219+
return new ServiceBusJmsConnectionFactoryFactory(serviceBusJmsProperties,
220+
factoryCustomizers.orderedStream().collect(Collectors.toList()))
221+
.createConnectionFactory(instanceFactory);
202222
}
203223
}
204224
}

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,29 @@
33

44
package com.azure.spring.cloud.autoconfigure.implementation.jms;
55

6-
import com.azure.core.credential.TokenCredential;
7-
import com.azure.identity.extensions.implementation.credential.TokenCredentialProviderOptions;
8-
import com.azure.identity.extensions.implementation.credential.provider.TokenCredentialProvider;
96
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
10-
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;
117
import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties;
128
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryCustomizer;
9+
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryFactory;
1310
import org.springframework.util.Assert;
1411
import org.springframework.util.StringUtils;
1512

16-
import java.lang.reflect.InvocationTargetException;
1713
import java.util.Collections;
1814
import java.util.List;
19-
import java.util.Properties;
20-
21-
import static com.azure.spring.cloud.autoconfigure.implementation.util.SpringPasswordlessPropertiesUtils.enhancePasswordlessProperties;
2215

2316
class ServiceBusJmsConnectionFactoryFactory {
2417
private final AzureServiceBusJmsProperties properties;
2518
private final List<AzureServiceBusJmsConnectionFactoryCustomizer> factoryCustomizers;
26-
private final TokenCredentialProvider tokenCredentialProvider;
2719

2820
ServiceBusJmsConnectionFactoryFactory(AzureServiceBusJmsProperties properties,
2921
List<AzureServiceBusJmsConnectionFactoryCustomizer> factoryCustomizers) {
3022
Assert.notNull(properties, "Properties must not be null");
3123
this.properties = properties;
3224
this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList();
33-
if (properties.isPasswordlessEnabled()) {
34-
Properties passwordlessProperties = properties.toPasswordlessProperties();
35-
enhancePasswordlessProperties(AzureServiceBusJmsProperties.PREFIX, properties, passwordlessProperties);
36-
this.tokenCredentialProvider = TokenCredentialProvider.createDefault(new TokenCredentialProviderOptions(passwordlessProperties));
37-
} else {
38-
this.tokenCredentialProvider = null;
39-
}
4025
}
4126

42-
<T extends ServiceBusJmsConnectionFactory> T createConnectionFactory(Class<T> factoryClass) {
43-
T factory = createConnectionFactoryInstance(factoryClass);
27+
ServiceBusJmsConnectionFactory createConnectionFactory(AzureServiceBusJmsConnectionFactoryFactory instanceFactory) {
28+
ServiceBusJmsConnectionFactory factory = createConnectionFactoryInstance(instanceFactory);
4429
setClientId(factory);
4530
setPrefetchPolicy(factory);
4631
customize(factory);
@@ -65,26 +50,8 @@ private <T extends ServiceBusJmsConnectionFactory> void setPrefetchPolicy(T fact
6550
String.valueOf(prefetchProperties.getTopicPrefetch()));
6651
}
6752

68-
private <T extends ServiceBusJmsConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass) {
69-
try {
70-
T factory;
71-
if (properties.isPasswordlessEnabled()) {
72-
String hostName =
73-
properties.getNamespace() + "." + properties.getProfile().getEnvironment().getServiceBusDomainName();
74-
TokenCredential tokenCredential = tokenCredentialProvider.get();
75-
factory = factoryClass.getConstructor(TokenCredential.class, String.class,
76-
ServiceBusJmsConnectionFactorySettings.class)
77-
.newInstance(tokenCredential, hostName,
78-
new ServiceBusJmsConnectionFactorySettings());
79-
} else {
80-
factory = factoryClass.getConstructor(String.class, ServiceBusJmsConnectionFactorySettings.class)
81-
.newInstance(properties.getConnectionString(),
82-
new ServiceBusJmsConnectionFactorySettings());
83-
}
84-
return factory;
85-
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
86-
throw new IllegalStateException("Unable to create JmsConnectionFactory", ex);
87-
}
53+
private ServiceBusJmsConnectionFactory createConnectionFactoryInstance(AzureServiceBusJmsConnectionFactoryFactory instanceFactory) {
54+
return instanceFactory.createServiceBusJmsConnectionFactory();
8855
}
8956

9057
private void customize(ServiceBusJmsConnectionFactory connectionFactory) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.spring.cloud.autoconfigure.jms;
5+
6+
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
7+
8+
/**
9+
* The interface used to define how the {@link ServiceBusJmsConnectionFactory} instance is created.
10+
*/
11+
@FunctionalInterface
12+
public interface AzureServiceBusJmsConnectionFactoryFactory {
13+
14+
/**
15+
* Creates an instance of {@link ServiceBusJmsConnectionFactory} or a subclass thereof.
16+
*
17+
* @return an instance of {@link ServiceBusJmsConnectionFactory}
18+
*/
19+
ServiceBusJmsConnectionFactory createServiceBusJmsConnectionFactory();
20+
}

sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
package com.azure.spring.cloud.autoconfigure.implementation.jms;
55

6+
import com.azure.core.credential.TokenCredential;
67
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
8+
import com.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;
79
import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
10+
import com.azure.spring.cloud.autoconfigure.jms.AzureServiceBusJmsConnectionFactoryFactory;
811
import jakarta.jms.Connection;
912
import jakarta.jms.ConnectionFactory;
1013
import jakarta.jms.Destination;
@@ -19,6 +22,7 @@
1922
import org.springframework.boot.jms.autoconfigure.JmsAutoConfiguration;
2023
import org.springframework.boot.test.context.FilteredClassLoader;
2124
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
25+
import org.springframework.context.annotation.Bean;
2226
import org.springframework.context.annotation.Configuration;
2327
import org.springframework.context.annotation.PropertySource;
2428
import org.springframework.jms.connection.CachingConnectionFactory;
@@ -180,6 +184,72 @@ void fallbackToServiceBusConnectionFactoryWhenNoCachingOrPoolClassesPresent(Stri
180184
});
181185
}
182186

187+
@Test
188+
void useCustomServiceBusJmsConnectionFactoryClassForServiceBusFactory() {
189+
this.contextRunner
190+
.withUserConfiguration(CustomConnectionFactoryClassConfiguration.class)
191+
.withPropertyValues(
192+
"spring.jms.servicebus.pricing-tier=premium",
193+
"spring.jms.servicebus.pool.enabled=false",
194+
"spring.jms.cache.enabled=false"
195+
)
196+
.run(context -> {
197+
assertThat(context).hasSingleBean(ServiceBusJmsConnectionFactory.class);
198+
assertThat(context.getBean(ServiceBusJmsConnectionFactory.class))
199+
.isInstanceOf(CustomServiceBusJmsConnectionFactory.class);
200+
});
201+
}
202+
203+
@Test
204+
void useCustomServiceBusJmsConnectionFactoryClassForPasswordlessServiceBusFactory() {
205+
this.contextRunner
206+
.withUserConfiguration(CustomConnectionFactoryClassConfiguration.class)
207+
.withPropertyValues(
208+
"spring.jms.servicebus.pricing-tier=premium",
209+
"spring.jms.servicebus.passwordless-enabled=true",
210+
"spring.jms.servicebus.namespace=test-namespace",
211+
"spring.jms.servicebus.pool.enabled=false",
212+
"spring.jms.cache.enabled=false"
213+
)
214+
.run(context -> {
215+
assertThat(context).hasSingleBean(ServiceBusJmsConnectionFactory.class);
216+
assertThat(context.getBean(ServiceBusJmsConnectionFactory.class))
217+
.isInstanceOf(CustomServiceBusJmsConnectionFactory.class);
218+
});
219+
}
220+
221+
@Test
222+
void useCustomServiceBusJmsConnectionFactoryClassForCachingFactory() {
223+
this.contextRunner
224+
.withUserConfiguration(CustomConnectionFactoryClassConfiguration.class)
225+
.withPropertyValues(
226+
"spring.jms.servicebus.pricing-tier=premium",
227+
"spring.jms.cache.enabled=true"
228+
)
229+
.run(context -> {
230+
assertThat(context).hasSingleBean(CachingConnectionFactory.class);
231+
CachingConnectionFactory cachingConnectionFactory = context.getBean(CachingConnectionFactory.class);
232+
assertThat(cachingConnectionFactory.getTargetConnectionFactory())
233+
.isInstanceOf(CustomServiceBusJmsConnectionFactory.class);
234+
});
235+
}
236+
237+
@Test
238+
void useCustomServiceBusJmsConnectionFactoryClassForPoolingFactory() {
239+
this.contextRunner
240+
.withUserConfiguration(CustomConnectionFactoryClassConfiguration.class)
241+
.withPropertyValues(
242+
"spring.jms.servicebus.pricing-tier=premium",
243+
"spring.jms.servicebus.pool.enabled=true"
244+
)
245+
.run(context -> {
246+
assertThat(context).hasSingleBean(JmsPoolConnectionFactory.class);
247+
JmsPoolConnectionFactory poolConnectionFactory = context.getBean(JmsPoolConnectionFactory.class);
248+
assertThat(poolConnectionFactory.getConnectionFactory())
249+
.isInstanceOf(CustomServiceBusJmsConnectionFactory.class);
250+
});
251+
}
252+
183253
@Test
184254
void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Exception {
185255
// Create mock objects for JMS components
@@ -276,4 +346,25 @@ private Session createServiceBusJmsSession(Session innerSession) throws Exceptio
276346
static class AdditionalPropertySourceConfiguration {
277347

278348
}
349+
350+
@Configuration
351+
static class CustomConnectionFactoryClassConfiguration {
352+
@Bean
353+
AzureServiceBusJmsConnectionFactoryFactory connectionFactoryFactory() {
354+
return () -> new CustomServiceBusJmsConnectionFactory(
355+
String.format(CONNECTION_STRING_FORMAT, "test-namespace"),
356+
new ServiceBusJmsConnectionFactorySettings());
357+
}
358+
}
359+
360+
static class CustomServiceBusJmsConnectionFactory extends ServiceBusJmsConnectionFactory {
361+
public CustomServiceBusJmsConnectionFactory(String connectionString, ServiceBusJmsConnectionFactorySettings settings) {
362+
super(connectionString, settings);
363+
}
364+
365+
public CustomServiceBusJmsConnectionFactory(TokenCredential tokenCredential, String host,
366+
ServiceBusJmsConnectionFactorySettings settings) {
367+
super(tokenCredential, host, settings);
368+
}
369+
}
279370
}

0 commit comments

Comments
 (0)