Move default subscription type to factory (#818)

This commit moves the default subscription type from the
`@PulsarListener` and `@ReactivePulsarListener` annotation to the
associated container factory (props) which allows the Spring Boot
`spring.pulsar.consumer.subscription-type` config prop to be respected.

See https://github.com/spring-projects/spring-boot/issues/42053
This commit is contained in:
Chris Bono
2024-08-29 23:39:50 -05:00
committed by GitHub
parent 01a95a70a2
commit 5946c95a17
10 changed files with 251 additions and 190 deletions

View File

@@ -15,9 +15,6 @@ Spring Boot provides this consumer factory which you can further configure by sp
TIP: The `spring.pulsar.consumer.subscription.name` property is ignored and is instead generated when not specified on the annotation.
TIP: The `spring.pulsar.consumer.subscription.type` property is ignored and is instead taken from the value on the annotation. However, you can set the `subscriptionType = {}` on the annotation to instead use the property value as the default.
Let us revisit the `PulsarListener` code snippet we saw in the quick-tour section:
[source, java]

View File

@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.List;
import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.client.api.SubscriptionType;
import org.springframework.core.log.LogAccessor;
import org.springframework.pulsar.reactive.core.ReactivePulsarConsumerFactory;
@@ -87,6 +88,7 @@ public class DefaultReactivePulsarListenerContainerFactory<T> implements Reactiv
ReactivePulsarContainerProperties<T> properties = new ReactivePulsarContainerProperties<>();
properties.setSchemaResolver(this.getContainerProperties().getSchemaResolver());
properties.setTopicResolver(this.getContainerProperties().getTopicResolver());
properties.setSubscriptionType(this.getContainerProperties().getSubscriptionType());
if (!CollectionUtils.isEmpty(endpoint.getTopics())) {
properties.setTopics(endpoint.getTopics());
@@ -103,8 +105,9 @@ public class DefaultReactivePulsarListenerContainerFactory<T> implements Reactiv
if (endpoint.getSubscriptionType() != null) {
properties.setSubscriptionType(endpoint.getSubscriptionType());
}
else {
properties.setSubscriptionType(this.containerProperties.getSubscriptionType());
// Default to Exclusive if not set on container props or endpoint
if (properties.getSubscriptionType() == null) {
properties.setSubscriptionType(SubscriptionType.Exclusive);
}
if (endpoint.getSchemaType() != null) {

View File

@@ -79,7 +79,7 @@ public @interface ReactivePulsarListener {
* @return single element array with the subscription type or empty array to indicate
* no type chosen by user
*/
SubscriptionType[] subscriptionType() default { SubscriptionType.Exclusive };
SubscriptionType[] subscriptionType() default {};
/**
* Pulsar schema type for this listener.

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed 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
*
* https://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 org.springframework.pulsar.reactive.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.pulsar.client.api.SubscriptionType;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.pulsar.reactive.core.ReactivePulsarConsumerFactory;
import org.springframework.pulsar.reactive.listener.ReactivePulsarContainerProperties;
/**
* Unit tests for {@link DefaultReactivePulsarListenerContainerFactory}.
*/
class DefaultReactivePulsarListenerContainerFactoryTests {
@SuppressWarnings("unchecked")
@Nested
class SubscriptionTypeFrom {
@Test
void factoryPropsUsedWhenNotSetOnEndpoint() {
var factoryProps = new ReactivePulsarContainerProperties<String>();
factoryProps.setSubscriptionType(SubscriptionType.Shared);
var containerFactory = new DefaultReactivePulsarListenerContainerFactory<String>(
mock(ReactivePulsarConsumerFactory.class), factoryProps);
var endpoint = mock(ReactivePulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Shared);
}
@Test
void endpointTakesPrecedenceOverFactoryProps() {
var factoryProps = new ReactivePulsarContainerProperties<String>();
factoryProps.setSubscriptionType(SubscriptionType.Shared);
var containerFactory = new DefaultReactivePulsarListenerContainerFactory<String>(
mock(ReactivePulsarConsumerFactory.class), factoryProps);
var endpoint = mock(ReactivePulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
when(endpoint.getSubscriptionType()).thenReturn(SubscriptionType.Failover);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Failover);
}
@Test
void defaultUsedWhenNotSetOnEndpointNorFactoryProps() {
var factoryProps = new ReactivePulsarContainerProperties<String>();
var containerFactory = new DefaultReactivePulsarListenerContainerFactory<String>(
mock(ReactivePulsarConsumerFactory.class), factoryProps);
var endpoint = mock(ReactivePulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Exclusive);
}
}
}

View File

@@ -72,8 +72,7 @@ import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.PulsarHeadersCustomObjectMapperTest.PulsarHeadersCustomObjectMapperTestConfig;
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.PulsarHeadersTest.PulsarListenerWithHeadersConfig;
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.StreamingListenerTestCases.StreamingListenerTestCasesConfig;
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SubscriptionTypeTests.WithDefaultType.WithDefaultTypeConfig;
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SubscriptionTypeTests.WithSpecificTypes.WithSpecificTypesConfig;
import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SubscriptionTypeTests.SubscriptionTypeTestsConfig;
import org.springframework.pulsar.reactive.support.MessageUtils;
import org.springframework.pulsar.support.PulsarHeaders;
import org.springframework.pulsar.support.header.JsonPulsarHeaderMapper;
@@ -816,117 +815,80 @@ class ReactivePulsarListenerTests extends ReactivePulsarListenerTestsBase {
}
@Nested
@ContextConfiguration(classes = SubscriptionTypeTestsConfig.class)
class SubscriptionTypeTests {
@Nested
@ContextConfiguration(classes = WithDefaultTypeConfig.class)
class WithDefaultType {
static final CountDownLatch latchTypeNotSet = new CountDownLatch(1);
static final CountDownLatch latchTypeNotSet = new CountDownLatch(1);
static final CountDownLatch latchTypeSetOnAnnotation = new CountDownLatch(1);
@Test
void whenTypeNotSetAnywhereThenFallbackTypeIsUsed(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
assertThat(consumerFactory.topicNameToConsumerSpec).hasEntrySatisfying("rpl-typeNotSetAnywhere-topic",
(consumerSpec) -> assertThat(consumerSpec.getSubscriptionType())
.isEqualTo(SubscriptionType.Exclusive));
pulsarTemplate.send("rpl-typeNotSetAnywhere-topic", "hello-rpl-typeNotSetAnywhere");
assertThat(latchTypeNotSet.await(10, TimeUnit.SECONDS)).isTrue();
}
@Configuration(proxyBeanMethods = false)
static class WithDefaultTypeConfig {
@ReactivePulsarListener(topics = "rpl-typeNotSetAnywhere-topic",
subscriptionName = "rpl-typeNotSetAnywhere-sub",
consumerCustomizer = "subscriptionInitialPositionEarliest")
Mono<Void> listenWithoutTypeSetAnywhere(String ignored) {
latchTypeNotSet.countDown();
return Mono.empty();
}
}
static final CountDownLatch latchTypeSetOnCustomizer = new CountDownLatch(1);
@Test
void defaultTypeFromContainerFactoryUsedWhenTypeNotSetAnywhere(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
var topic = "rpl-latchTypeNotSet-topic";
assertThat(consumerFactory.getSpec(topic)).extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Exclusive);
pulsarTemplate.send(topic, "hello-" + topic);
assertThat(latchTypeNotSet.await(5, TimeUnit.SECONDS)).isTrue();
}
@Nested
@ContextConfiguration(classes = WithSpecificTypesConfig.class)
class WithSpecificTypes {
@Test
void typeSetOnAnnotationOverridesDefaultTypeFromContainerFactory(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
var topic = "rpl-typeSetOnAnnotation-topic";
assertThat(consumerFactory.getSpec(topic)).extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Key_Shared);
pulsarTemplate.send(topic, "hello-" + topic);
assertThat(latchTypeSetOnAnnotation.await(5, TimeUnit.SECONDS)).isTrue();
}
static final CountDownLatch latchTypeSetConsumerFactory = new CountDownLatch(1);
@Test
void typeSetOnCustomizerOverridesTypeSetOnAnnotation(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
var topic = "rpl-typeSetOnCustomizer-topic";
assertThat(consumerFactory.getSpec(topic)).extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Failover);
pulsarTemplate.send(topic, "hello-" + topic);
assertThat(latchTypeSetOnCustomizer.await(5, TimeUnit.SECONDS)).isTrue();
}
static final CountDownLatch latchTypeSetAnnotation = new CountDownLatch(1);
@Configuration(proxyBeanMethods = false)
static class SubscriptionTypeTestsConfig {
static final CountDownLatch latchWithCustomizer = new CountDownLatch(1);
@Test
void whenTypeSetOnlyInConsumerFactoryThenConsumerFactoryTypeIsUsed(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
assertThat(consumerFactory.getSpec("rpl-typeSetConsumerFactory-topic"))
.extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Shared);
pulsarTemplate.send("rpl-typeSetConsumerFactory-topic", "hello-rpl-typeSetConsumerFactory");
assertThat(latchTypeSetConsumerFactory.await(10, TimeUnit.SECONDS)).isTrue();
@Bean
ReactiveMessageConsumerBuilderCustomizer<String> consumerFactoryDefaultSubTypeCustomizer() {
return (b) -> b.subscriptionType(SubscriptionType.Shared);
}
@Test
void whenTypeSetOnAnnotationThenAnnotationTypeIsUsed(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
assertThat(consumerFactory.getSpec("rpl-typeSetAnnotation-topic"))
.extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Key_Shared);
pulsarTemplate.send("rpl-typeSetAnnotation-topic", "hello-rpl-typeSetAnnotation");
assertThat(latchTypeSetAnnotation.await(10, TimeUnit.SECONDS)).isTrue();
@ReactivePulsarListener(topics = "rpl-latchTypeNotSet-topic", subscriptionName = "rpl-latchTypeNotSet-sub",
consumerCustomizer = "subscriptionInitialPositionEarliest")
Mono<Void> listenWithoutTypeSetAnywhere(String ignored) {
latchTypeNotSet.countDown();
return Mono.empty();
}
@Test
void whenTypeSetWithCustomizerThenCustomizerTypeIsUsed(
@Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception {
assertThat(consumerFactory.getSpec("rpl-typeSetCustomizer-topic"))
.extracting(ReactiveMessageConsumerSpec::getSubscriptionType)
.isEqualTo(SubscriptionType.Failover);
pulsarTemplate.send("rpl-typeSetCustomizer-topic", "hello-rpl-typeSetCustomizer");
assertThat(latchWithCustomizer.await(10, TimeUnit.SECONDS)).isTrue();
@ReactivePulsarListener(topics = "rpl-typeSetOnAnnotation-topic",
subscriptionName = "rpl-typeSetOnAnnotation-sub", subscriptionType = SubscriptionType.Key_Shared,
consumerCustomizer = "subscriptionInitialPositionEarliest")
Mono<Void> listenWithTypeSetOnAnnotation(String ignored) {
latchTypeSetOnAnnotation.countDown();
return Mono.empty();
}
@Configuration(proxyBeanMethods = false)
static class WithSpecificTypesConfig {
@Bean
ReactiveMessageConsumerBuilderCustomizer<String> consumerFactoryDefaultSubTypeCustomizer() {
return (b) -> b.subscriptionType(SubscriptionType.Shared);
}
@ReactivePulsarListener(topics = "rpl-typeSetConsumerFactory-topic",
subscriptionName = "rpl-typeSetConsumerFactory-sub", subscriptionType = {},
consumerCustomizer = "subscriptionInitialPositionEarliest")
Mono<Void> listenWithTypeSetOnlyOnConsumerFactory(String ignored) {
latchTypeSetConsumerFactory.countDown();
return Mono.empty();
}
@ReactivePulsarListener(topics = "rpl-typeSetAnnotation-topic",
subscriptionName = "rpl-typeSetAnnotation-sub", subscriptionType = SubscriptionType.Key_Shared,
consumerCustomizer = "subscriptionInitialPositionEarliest")
Mono<Void> listenWithTypeSetOnAnnotation(String ignored) {
latchTypeSetAnnotation.countDown();
return Mono.empty();
}
@ReactivePulsarListener(topics = "rpl-typeSetCustomizer-topic",
subscriptionName = "rpl-typeSetCustomizer-sub", subscriptionType = SubscriptionType.Key_Shared,
consumerCustomizer = "myCustomizer")
Mono<Void> listenWithTypeSetInCustomizer(String ignored) {
latchWithCustomizer.countDown();
return Mono.empty();
}
@Bean
public ReactivePulsarListenerMessageConsumerBuilderCustomizer<String> myCustomizer() {
return cb -> cb.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.subscriptionType(SubscriptionType.Failover);
}
@ReactivePulsarListener(topics = "rpl-typeSetOnCustomizer-topic",
subscriptionName = "rpl-typeSetOnCustomizer-sub", subscriptionType = SubscriptionType.Key_Shared,
consumerCustomizer = "myCustomizer")
Mono<Void> listenWithTypeSetOnCustomizer(String ignored) {
latchTypeSetOnCustomizer.countDown();
return Mono.empty();
}
@Bean
public ReactivePulsarListenerMessageConsumerBuilderCustomizer<String> myCustomizer() {
return cb -> cb.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.subscriptionType(SubscriptionType.Failover);
}
}

View File

@@ -81,7 +81,7 @@ public @interface PulsarListener {
* @return single element array with the subscription type or empty array to indicate
* no type chosen by user
*/
SubscriptionType[] subscriptionType() default { SubscriptionType.Exclusive };
SubscriptionType[] subscriptionType() default {};
/**
* Pulsar schema type for this listener.

View File

@@ -20,6 +20,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import org.apache.pulsar.client.api.SubscriptionType;
import org.springframework.pulsar.core.PulsarConsumerFactory;
import org.springframework.pulsar.listener.ConcurrentPulsarMessageListenerContainer;
import org.springframework.pulsar.listener.PulsarContainerProperties;
@@ -74,6 +76,7 @@ public class ConcurrentPulsarListenerContainerFactory<T>
PulsarContainerProperties properties = new PulsarContainerProperties();
properties.setSchemaResolver(this.getContainerProperties().getSchemaResolver());
properties.setTopicResolver(this.getContainerProperties().getTopicResolver());
properties.setSubscriptionType(this.getContainerProperties().getSubscriptionType());
var parentTxnProps = this.getContainerProperties().transactions();
var childTxnProps = properties.transactions();
@@ -102,6 +105,10 @@ public class ConcurrentPulsarListenerContainerFactory<T>
if (endpoint.getSubscriptionType() != null) {
properties.setSubscriptionType(endpoint.getSubscriptionType());
}
// Default to Exclusive if not set on container props or endpoint
if (properties.getSubscriptionType() == null) {
properties.setSubscriptionType(SubscriptionType.Exclusive);
}
properties.setSchemaType(endpoint.getSchemaType());

View File

@@ -304,11 +304,9 @@ public class DefaultPulsarMessageListenerContainer<T> extends AbstractPulsarMess
topicNames, this.containerProperties.getSubscriptionName(), properties, customizers);
Assert.state(this.consumer != null, "Unable to create a consumer");
// If subtype is null - update it based on the actual subtype of the
// underlying consumer
if (this.subscriptionType == null) {
updateSubscriptionTypeFromConsumer(this.consumer);
}
// Update sub type from underlying consumer as customizer from annotation
// may have updated it
updateSubscriptionTypeFromConsumer(this.consumer);
}
catch (PulsarException e) {
DefaultPulsarMessageListenerContainer.this.logger.error(e, () -> "Pulsar exception.");

View File

@@ -212,6 +212,51 @@ public class ConcurrentPulsarMessageListenerContainerTests {
Consumer<String> consumer, ConcurrentPulsarMessageListenerContainer<String> concurrentContainer) {
}
@SuppressWarnings("unchecked")
@Nested
class SubscriptionTypeFrom {
@Test
void factoryPropsUsedWhenNotSetOnEndpoint() {
var factoryProps = new PulsarContainerProperties();
factoryProps.setSubscriptionType(SubscriptionType.Shared);
var containerFactory = new ConcurrentPulsarListenerContainerFactory<String>(
mock(PulsarConsumerFactory.class), factoryProps);
var endpoint = mock(PulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Shared);
}
@Test
void endpointTakesPrecedenceOverFactoryProps() {
var factoryProps = new PulsarContainerProperties();
factoryProps.setSubscriptionType(SubscriptionType.Shared);
var containerFactory = new ConcurrentPulsarListenerContainerFactory<String>(
mock(PulsarConsumerFactory.class), factoryProps);
var endpoint = mock(PulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
when(endpoint.getSubscriptionType()).thenReturn(SubscriptionType.Failover);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Failover);
}
@Test
void defaultUsedWhenNotSetOnEndpointNorFactoryProps() {
var factoryProps = new PulsarContainerProperties();
var containerFactory = new ConcurrentPulsarListenerContainerFactory<String>(
mock(PulsarConsumerFactory.class), factoryProps);
var endpoint = mock(PulsarListenerEndpoint.class);
when(endpoint.getConcurrency()).thenReturn(1);
var createdContainer = containerFactory.createListenerContainer(endpoint);
assertThat(createdContainer.getContainerProperties().getSubscriptionType())
.isEqualTo(SubscriptionType.Exclusive);
}
}
@Nested
class ObservationConfigurationTests {

View File

@@ -73,8 +73,7 @@ import org.springframework.pulsar.core.PulsarTemplate;
import org.springframework.pulsar.core.SchemaResolver;
import org.springframework.pulsar.core.TopicResolver;
import org.springframework.pulsar.listener.PulsarListenerTests.PulsarHeadersCustomObjectMapperTest.PulsarHeadersCustomObjectMapperTestConfig;
import org.springframework.pulsar.listener.PulsarListenerTests.SubscriptionTypeTests.WithDefaultType.WithDefaultTypeConfig;
import org.springframework.pulsar.listener.PulsarListenerTests.SubscriptionTypeTests.WithSpecificTypes.WithSpecificTypesConfig;
import org.springframework.pulsar.listener.PulsarListenerTests.SubscriptionTypeTests.SubscriptionTypeTestsConfig;
import org.springframework.pulsar.support.PulsarHeaders;
import org.springframework.pulsar.support.header.JsonPulsarHeaderMapper;
import org.springframework.pulsar.test.model.UserPojo;
@@ -1055,102 +1054,71 @@ class PulsarListenerTests extends PulsarListenerTestsBase {
}
@Nested
@ContextConfiguration(classes = SubscriptionTypeTestsConfig.class)
class SubscriptionTypeTests {
@SuppressWarnings("rawtypes")
private static AbstractObjectAssert<?, ?> assertSubscriptionType(Consumer<?> consumer) {
return assertThat(consumer)
.extracting("conf", InstanceOfAssertFactories.type(ConsumerConfigurationData.class))
.extracting(ConsumerConfigurationData::getSubscriptionType);
static final CountDownLatch latchTypeNotSet = new CountDownLatch(1);
static final CountDownLatch latchTypeSetOnAnnotation = new CountDownLatch(1);
static final CountDownLatch latchTypeSetOnCustomizer = new CountDownLatch(1);
@Test
void defaultTypeFromContainerFactoryUsedWhenTypeNotSetAnywhere() throws Exception {
pulsarTemplate.send("latchTypeNotSet-topic", "hello-latchTypeNotSet");
assertThat(latchTypeNotSet.await(5, TimeUnit.SECONDS)).isTrue();
}
@Nested
@ContextConfiguration(classes = WithDefaultTypeConfig.class)
class WithDefaultType {
static final CountDownLatch latchTypeNotSet = new CountDownLatch(1);
@Test
void whenTypeNotSetAnywhereThenFallbackTypeIsUsed() throws Exception {
pulsarTemplate.send("typeNotSetAnywhere-topic", "hello-typeNotSetAnywhere");
assertThat(latchTypeNotSet.await(5, TimeUnit.SECONDS)).isTrue();
}
@Configuration(proxyBeanMethods = false)
static class WithDefaultTypeConfig {
@PulsarListener(topics = "typeNotSetAnywhere-topic", subscriptionName = "typeNotSetAnywhere-sub")
void listenWithoutTypeSetAnywhere(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Exclusive);
latchTypeNotSet.countDown();
}
}
@Test
void typeSetOnAnnotationOverridesDefaultTypeFromContainerFactory() throws Exception {
pulsarTemplate.send("typeSetOnAnnotation-topic", "hello-typeSetOnAnnotation");
assertThat(latchTypeSetOnAnnotation.await(5, TimeUnit.SECONDS)).isTrue();
}
@Nested
@ContextConfiguration(classes = WithSpecificTypesConfig.class)
class WithSpecificTypes {
@Test
void typeSetOnCustomizerOverridesTypeSetOnAnnotation() throws Exception {
pulsarTemplate.send("typeSetOnCustomizer-topic", "hello-typeSetOnCustomizer");
assertThat(latchTypeSetOnCustomizer.await(5, TimeUnit.SECONDS)).isTrue();
}
static final CountDownLatch latchTypeSetConsumerFactory = new CountDownLatch(1);
@Configuration(proxyBeanMethods = false)
static class SubscriptionTypeTestsConfig {
static final CountDownLatch latchTypeSetAnnotation = new CountDownLatch(1);
static final CountDownLatch latchWithCustomizer = new CountDownLatch(1);
@Test
void whenTypeSetOnlyInConsumerFactoryThenConsumerFactoryTypeIsUsed() throws Exception {
pulsarTemplate.send("typeSetConsumerFactory-topic", "hello-typeSetConsumerFactory");
assertThat(latchTypeSetConsumerFactory.await(5, TimeUnit.SECONDS)).isTrue();
@Bean
ConsumerBuilderCustomizer<String> consumerFactoryCustomizerSubTypeIsIgnored() {
return (b) -> b.subscriptionType(SubscriptionType.Shared);
}
@Test
void whenTypeSetOnAnnotationThenAnnotationTypeIsUsed() throws Exception {
pulsarTemplate.send("typeSetAnnotation-topic", "hello-typeSetAnnotation");
assertThat(latchTypeSetAnnotation.await(5, TimeUnit.SECONDS)).isTrue();
@PulsarListener(topics = "latchTypeNotSet-topic", subscriptionName = "latchTypeNotSet-sub")
void listenWithTypeNotSet(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Exclusive);
latchTypeNotSet.countDown();
}
@Test
void whenTypeSetWithCustomizerThenCustomizerTypeIsUsed() throws Exception {
pulsarTemplate.send("typeSetCustomizer-topic", "hello-typeSetCustomizer");
assertThat(latchWithCustomizer.await(5, TimeUnit.SECONDS)).isTrue();
@PulsarListener(topics = "typeSetOnAnnotation-topic", subscriptionName = "typeSetOnAnnotation-sub",
subscriptionType = SubscriptionType.Key_Shared)
void listenWithTypeSetOnAnnotation(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Key_Shared);
latchTypeSetOnAnnotation.countDown();
}
@Configuration(proxyBeanMethods = false)
static class WithSpecificTypesConfig {
@PulsarListener(topics = "typeSetOnCustomizer-topic", subscriptionName = "typeSetOnCustomizer-sub",
subscriptionType = SubscriptionType.Key_Shared, consumerCustomizer = "myCustomizer")
void listenWithTypeSetOnCustomizer(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Failover);
latchTypeSetOnCustomizer.countDown();
}
@Bean
ConsumerBuilderCustomizer<String> consumerFactoryDefaultSubTypeCustomizer() {
return (b) -> b.subscriptionType(SubscriptionType.Shared);
}
@PulsarListener(topics = "typeSetConsumerFactory-topic",
subscriptionName = "typeSetConsumerFactory-sub", subscriptionType = {})
void listenWithTypeSetOnlyOnConsumerFactory(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Shared);
latchTypeSetConsumerFactory.countDown();
}
@PulsarListener(topics = "typeSetAnnotation-topic", subscriptionName = "typeSetAnnotation-sub",
subscriptionType = SubscriptionType.Key_Shared)
void listenWithTypeSetOnAnnotation(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Key_Shared);
latchTypeSetAnnotation.countDown();
}
@PulsarListener(topics = "typeSetCustomizer-topic", subscriptionName = "typeSetCustomizer-sub",
subscriptionType = SubscriptionType.Key_Shared, consumerCustomizer = "myCustomizer")
void listenWithTypeSetInCustomizer(String ignored, Consumer<String> consumer) {
assertSubscriptionType(consumer).isEqualTo(SubscriptionType.Failover);
latchWithCustomizer.countDown();
}
@Bean
public PulsarListenerConsumerBuilderCustomizer<String> myCustomizer() {
return cb -> cb.subscriptionType(SubscriptionType.Failover);
}
@Bean
public PulsarListenerConsumerBuilderCustomizer<String> myCustomizer() {
return cb -> cb.subscriptionType(SubscriptionType.Failover);
}
@SuppressWarnings("rawtypes")
private static AbstractObjectAssert<?, ?> assertSubscriptionType(Consumer<?> consumer) {
return assertThat(consumer)
.extracting("conf", InstanceOfAssertFactories.type(ConsumerConfigurationData.class))
.extracting(ConsumerConfigurationData::getSubscriptionType);
}
}