Skip to content

Commit 916bed5

Browse files
authored
Add new property session idle timeout for service bus session processor client (Azure#44630)
1 parent 41acd71 commit 916bed5

File tree

10 files changed

+195
-2
lines changed

10 files changed

+195
-2
lines changed

sdk/spring/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module.
1616
#### Bugs Fixed
1717
- Custom `ObjectMapper` bean does not work for received messages. [#37796](https://github.com/Azure/azure-sdk-for-java/issues/37796).
1818

19+
#### Features Added
20+
- Support new property `sessionIdleTimeout` for `ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder` [#44414](https://github.com/Azure/azure-sdk-for-java/issues/44414).
21+
1922
### Spring Cloud Azure Starter Key Vault
2023
This section includes changes in `spring-cloud-azure-starter-keyvault` module.
2124

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/servicebus/properties/AzureServiceBusProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ public static class Processor extends Consumer implements ServiceBusProcessorCli
268268
*/
269269
private boolean autoStartup = true;
270270

271+
/**
272+
* Sets the maximum amount of time to wait for a message to be received for the currently active session.
273+
*/
274+
private Duration sessionIdleTimeout;
275+
271276
public Integer getMaxConcurrentCalls() {
272277
return maxConcurrentCalls;
273278
}
@@ -291,6 +296,14 @@ public boolean isAutoStartup() {
291296
public void setAutoStartup(boolean autoStartup) {
292297
this.autoStartup = autoStartup;
293298
}
299+
300+
public Duration getSessionIdleTimeout() {
301+
return sessionIdleTimeout;
302+
}
303+
304+
public void setSessionIdleTimeout(Duration sessionIdleTimeout) {
305+
this.sessionIdleTimeout = sessionIdleTimeout;
306+
}
294307
}
295308

296309

sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/servicebus/factory/ServiceBusSessionProcessorClientBuilderFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ protected void configureService(ServiceBusClientBuilder.ServiceBusSessionProcess
9191
propertyMapper.from(properties.getAutoComplete()).whenFalse().to(t -> builder.disableAutoComplete());
9292
propertyMapper.from(properties.getMaxConcurrentCalls()).to(builder::maxConcurrentCalls);
9393
propertyMapper.from(properties.getMaxConcurrentSessions()).to(builder::maxConcurrentSessions);
94+
propertyMapper.from(properties.getSessionIdleTimeout()).to(builder::sessionIdleTimeout);
9495

9596
propertyMapper.from(this.errorHandler).to(builder::processError);
9697

sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/servicebus/properties/ServiceBusProcessorClientProperties.java

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

44
package com.azure.spring.cloud.service.implementation.servicebus.properties;
55

6+
import java.time.Duration;
7+
68
/**
79
*
810
*/
@@ -12,4 +14,6 @@ public interface ServiceBusProcessorClientProperties extends ServiceBusReceiverC
1214

1315
Integer getMaxConcurrentSessions();
1416

17+
Duration getSessionIdleTimeout();
18+
1519
}

sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/AzureGenericServiceClientBuilderFactoryBaseTests.java

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,94 @@
55

66
import com.azure.spring.cloud.core.implementation.factory.AzureServiceClientBuilderFactory;
77
import com.azure.spring.cloud.core.implementation.properties.AzureSdkProperties;
8+
import org.junit.jupiter.api.Test;
89

9-
public abstract class AzureGenericServiceClientBuilderFactoryBaseTests<P extends AzureSdkProperties, F extends AzureServiceClientBuilderFactory<?>> {
10+
import java.lang.reflect.Method;
11+
import java.lang.reflect.Parameter;
12+
import java.time.Duration;
13+
import java.util.Arrays;
14+
import java.util.HashSet;
15+
import java.util.Map;
16+
import java.util.Set;
17+
import java.util.function.Consumer;
18+
import java.util.function.Function;
19+
import java.util.function.Predicate;
20+
import java.util.stream.Collectors;
21+
22+
import static com.azure.spring.cloud.core.implementation.util.ClassUtils.isPrimitive;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
public abstract class AzureGenericServiceClientBuilderFactoryBaseTests<P extends AzureSdkProperties,
26+
F extends AzureServiceClientBuilderFactory<?>> {
1027

1128
protected abstract P createMinimalServiceProperties();
29+
1230
protected abstract F createClientBuilderFactoryWithMockBuilder(P properties);
1331

32+
private static final Set<Class<?>> IGNORED_CLASS = Set.of(Object.class, Class.class, Enum.class, String.class,
33+
Boolean.class, Integer.class, Long.class, Duration.class);
34+
private static final Set<String> IGNORED_METHOD_NAMES =
35+
Arrays.stream(Object.class.getMethods()).map(Method::getName).collect(Collectors.toSet());
36+
private static final Set<Class<?>> BUILDER_IGNORED_PARAMETER_TYPES = Set.of(Consumer.class);
37+
private static final Set<String> BUILDER_IGNORED_METHOD_NAME_PREFIX = Set.of("build", "process");
38+
private static final Function<String, String> EXTRACT_METHOD_NAME = methodName -> {
39+
if (methodName.startsWith("is")) {
40+
return methodName.substring("is".length());
41+
} else {
42+
return methodName.substring("set".length());
43+
}
44+
};
45+
46+
public static Set<String> listSupportedProperties(Class<?> propertiesClass) {
47+
Set<Method> classMethodSet = new HashSet<>();
48+
listClassMethods(classMethodSet, method -> Set.of(method.getReturnType()), propertiesClass,
49+
method -> method.getName().startsWith("is") || method.getName().startsWith("get"));
50+
return classMethodSet.stream()
51+
.map(Method::getName)
52+
.map(EXTRACT_METHOD_NAME)
53+
.collect(Collectors.toSet());
54+
}
55+
56+
public static Set<String> listBuilderProperties(Class<?> builderClass) {
57+
Set<Method> classMethodSet = new HashSet<>();
58+
listClassMethods(classMethodSet,
59+
method -> Arrays.stream(method.getParameters())
60+
.map(Parameter::getType)
61+
.filter(type -> !BUILDER_IGNORED_PARAMETER_TYPES.contains(type))
62+
.collect(Collectors.toSet()),
63+
builderClass, method -> BUILDER_IGNORED_METHOD_NAME_PREFIX.stream()
64+
.noneMatch(prefix -> method.getName().startsWith(prefix)));
65+
return classMethodSet.stream().map(Method::getName).collect(Collectors.toSet());
66+
}
67+
68+
public static void listClassMethods(Set<Method> classMethodSet, Function<Method, Set<Class<?>>> iterationType,
69+
Class<?> propertiesClass, Predicate<Method> filter) {
70+
if (isPrimitive(propertiesClass) || propertiesClass.isEnum() || IGNORED_CLASS.contains(propertiesClass)) {
71+
return;
72+
}
73+
74+
Method[] propertiesMethods = propertiesClass.getMethods();
75+
Set<Method> methodSet = Arrays.stream(propertiesMethods)
76+
.filter(filter)
77+
.filter(method -> !IGNORED_METHOD_NAMES.contains(method.getName()))
78+
.collect(Collectors.toSet());
79+
if (iterationType != null) {
80+
methodSet.forEach(method -> {
81+
for (Class<?> subClass : iterationType.apply(method)) {
82+
listClassMethods(classMethodSet, iterationType, subClass, filter);
83+
}
84+
});
85+
}
86+
System.out.println("[" + propertiesClass.getSimpleName() + "] class property names: \n"
87+
+ methodSet.stream().map(Method::getName).map(EXTRACT_METHOD_NAME).collect(Collectors.toSet()));
88+
classMethodSet.addAll(methodSet);
89+
90+
Class<?> superclass = propertiesClass.getSuperclass();
91+
if (superclass != null) {
92+
listClassMethods(classMethodSet, iterationType, superclass, filter);
93+
}
94+
}
95+
1496
protected F factoryWithMinimalSettings() {
1597
P properties = createMinimalServiceProperties();
1698
return createClientBuilderFactoryWithMockBuilder(properties);
@@ -64,4 +146,44 @@ private P createManagedIdentityCredentialAwareServiceProperties(P properties) {
64146
return properties;
65147
}
66148

149+
@Test
150+
void supportSdkBuilderAllProperties() {
151+
verifyNoUnsupportedPropertiesFromBuilderClass();
152+
}
153+
154+
public PropertiesIntegrityParameters getParametersForPropertiesIntegrity() {
155+
// override by sub builder factory class
156+
return null;
157+
}
158+
159+
public void verifyNoUnsupportedPropertiesFromBuilderClass() {
160+
PropertiesIntegrityParameters parameters = getParametersForPropertiesIntegrity();
161+
if (parameters == null) {
162+
return;
163+
}
164+
165+
Set<String> supportedProperties = listSupportedProperties(parameters.propertiesClass());
166+
Set<String> builderProperties = listBuilderProperties(parameters.sdkBinderClass());
167+
Set<String> lowCaseSupportedProperties =
168+
supportedProperties.stream().map(String::toLowerCase).collect(Collectors.toSet());
169+
Map<String, String> namingFromBinderToProperties = parameters.propertyNameMappingForBinder();
170+
Set<String> unsupportedPropertyNames =
171+
builderProperties.stream()
172+
.map(String::toLowerCase)
173+
.filter(builderPropertyName -> {
174+
String targetName = builderPropertyName.toLowerCase();
175+
return (!lowCaseSupportedProperties.contains(targetName) && !namingFromBinderToProperties.containsKey(targetName))
176+
|| (namingFromBinderToProperties.containsKey(targetName) && !lowCaseSupportedProperties.contains(namingFromBinderToProperties.get(targetName)));
177+
})
178+
.collect(Collectors.toSet());
179+
System.out.println("Properties class supported property names: \n" + supportedProperties);
180+
System.out.println("Builder class owned property names: \n" + builderProperties);
181+
System.out.println("Unsupported property names: \n" + unsupportedPropertyNames);
182+
assertTrue(unsupportedPropertyNames.isEmpty());
183+
}
184+
185+
public record PropertiesIntegrityParameters(Class<?> propertiesClass, Class<?> sdkBinderClass,
186+
Map<String, String> propertyNameMappingForBinder) {
187+
188+
}
67189
}

sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/servicebus/factory/ServiceBusSessionProcessorClientBuilderFactoryTests.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.junit.jupiter.api.Test;
1616

1717
import java.time.Duration;
18+
import java.util.HashMap;
19+
import java.util.Map;
1820

1921
import static org.mockito.ArgumentMatchers.any;
2022
import static org.mockito.Mockito.doReturn;
@@ -28,18 +30,30 @@ class ServiceBusSessionProcessorClientBuilderFactoryTests extends AbstractServic
2830
ServiceBusProcessorClientTestProperties,
2931
ServiceBusSessionProcessorClientBuilderFactory> {
3032

33+
@Override
34+
public PropertiesIntegrityParameters getParametersForPropertiesIntegrity() {
35+
Map<String, String> namingFromBinderToProperties = new HashMap<>();
36+
namingFromBinderToProperties.put("queuename", "entityname");
37+
namingFromBinderToProperties.put("topicname", "entityname");
38+
namingFromBinderToProperties.put("disableautocomplete", "autocomplete");
39+
return new PropertiesIntegrityParameters(ServiceBusProcessorClientTestProperties.class,
40+
ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder.class, namingFromBinderToProperties);
41+
}
42+
3143
@Test
32-
void queueConfigured() {
44+
void configured() {
3345
ServiceBusProcessorClientTestProperties properties = new ServiceBusProcessorClientTestProperties();
3446
properties.setNamespace("test-namespace");
3547
properties.setEntityType(ServiceBusEntityType.QUEUE);
3648
properties.setEntityName("test-queue");
49+
properties.setSessionIdleTimeout(Duration.ofSeconds(10));
3750

3851
final ServiceBusSessionProcessorClientBuilderFactory factory = createClientBuilderFactoryWithMockBuilder(properties);
3952
final ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder builder = factory.build();
4053
builder.buildProcessorClient();
4154

4255
verify(builder, times(1)).queueName("test-queue");
56+
verify(builder, times(1)).sessionIdleTimeout(Duration.ofSeconds(10));
4357
}
4458

4559
@Test

sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/servicebus/properties/ServiceBusProcessorClientTestProperties.java

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

44
package com.azure.spring.cloud.service.implementation.servicebus.properties;
55

6+
import java.time.Duration;
7+
68
public class ServiceBusProcessorClientTestProperties extends ServiceBusReceiverClientTestProperties
79
implements ServiceBusProcessorClientProperties {
810

911
private Integer maxConcurrentCalls;
1012
private Integer maxConcurrentSessions;
13+
private Duration sessionIdleTimeout;
1114

1215
@Override
1316
public Integer getMaxConcurrentCalls() {
@@ -26,4 +29,13 @@ public Integer getMaxConcurrentSessions() {
2629
public void setMaxConcurrentSessions(Integer maxConcurrentSessions) {
2730
this.maxConcurrentSessions = maxConcurrentSessions;
2831
}
32+
33+
@Override
34+
public Duration getSessionIdleTimeout() {
35+
return sessionIdleTimeout;
36+
}
37+
38+
public void setSessionIdleTimeout(Duration sessionIdleTimeout) {
39+
this.sessionIdleTimeout = sessionIdleTimeout;
40+
}
2941
}

sdk/spring/spring-messaging-azure-servicebus/src/main/java/com/azure/spring/messaging/servicebus/core/properties/ProcessorProperties.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import com.azure.spring.cloud.service.implementation.servicebus.properties.ServiceBusProcessorClientProperties;
77

8+
import java.time.Duration;
9+
810
/**
911
* A service bus processor related properties.
1012
*/
@@ -18,6 +20,7 @@ public ProcessorProperties() {
1820

1921
private Integer maxConcurrentCalls;
2022
private Integer maxConcurrentSessions;
23+
private Duration sessionIdleTimeout;
2124

2225
@Override
2326
public Integer getMaxConcurrentCalls() {
@@ -44,4 +47,17 @@ public Integer getMaxConcurrentSessions() {
4447
public void setMaxConcurrentSessions(Integer maxConcurrentSessions) {
4548
this.maxConcurrentSessions = maxConcurrentSessions;
4649
}
50+
51+
@Override
52+
public Duration getSessionIdleTimeout() {
53+
return sessionIdleTimeout;
54+
}
55+
56+
/**
57+
* Sets the maximum amount of time to wait for a message to be received for the currently active session.
58+
* @param sessionIdleTimeout the idle timeout for active session.
59+
*/
60+
public void setSessionIdleTimeout(Duration sessionIdleTimeout) {
61+
this.sessionIdleTimeout = sessionIdleTimeout;
62+
}
4763
}

sdk/spring/spring-messaging-azure-servicebus/src/main/java/com/azure/spring/messaging/servicebus/implementation/properties/merger/ProcessorPropertiesMerger.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static void copyProcessorPropertiesIfNotNull(ProcessorProperties source,
4949
propertyMapper.from(source.getCustomEndpointAddress()).to(target::setCustomEndpointAddress);
5050

5151
propertyMapper.from(source.getSessionEnabled()).to(target::setSessionEnabled);
52+
propertyMapper.from(source.getSessionIdleTimeout()).to(target::setSessionIdleTimeout);
5253
propertyMapper.from(source.getAutoComplete()).to(target::setAutoComplete);
5354
propertyMapper.from(source.getPrefetchCount()).to(target::setPrefetchCount);
5455
propertyMapper.from(source.getSubQueue()).to(target::setSubQueue);

sdk/spring/spring-messaging-azure-servicebus/src/test/java/com/azure/spring/messaging/servicebus/implementation/properties/merger/ProcessorPropertiesParentMergerTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@
99
import com.azure.spring.messaging.servicebus.core.properties.ProcessorProperties;
1010
import org.junit.jupiter.api.Test;
1111

12+
import java.time.Duration;
13+
1214
import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE_CHINA;
1315
import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT;
1416
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
1518

1619
public class ProcessorPropertiesParentMergerTests {
1720
private final ProcessorPropertiesParentMerger merger = new ProcessorPropertiesParentMerger();
1821

1922
@Test
2023
void childNotProvidedShouldUseParent() {
2124
ProcessorProperties child = new ProcessorProperties();
25+
child.setSessionEnabled(true);
26+
child.setSessionIdleTimeout(Duration.ofSeconds(10));
2227

2328
String customEndpoint = "https://test.address.com:443";
2429
NamespaceProperties parent = new NamespaceProperties();
@@ -39,6 +44,8 @@ void childNotProvidedShouldUseParent() {
3944
assertEquals("parent-domain", result.getDomainName());
4045
assertEquals(customEndpoint, result.getCustomEndpointAddress());
4146
assertEquals(AmqpTransportType.AMQP_WEB_SOCKETS, result.getClient().getTransportType());
47+
assertTrue(result.getSessionEnabled());
48+
assertEquals(Duration.ofSeconds(10), result.getSessionIdleTimeout());
4249
}
4350

4451
@Test

0 commit comments

Comments
 (0)