|
20 | 20 |
|
21 | 21 | import java.nio.charset.StandardCharsets; |
22 | 22 | import java.time.Duration; |
23 | | -import java.util.Collections; |
| 23 | +import java.util.HashMap; |
24 | 24 | import java.util.List; |
| 25 | +import java.util.Map; |
25 | 26 | import java.util.Objects; |
26 | 27 | import java.util.concurrent.BlockingQueue; |
27 | 28 | import java.util.concurrent.CountDownLatch; |
|
45 | 46 | import org.apache.pulsar.common.schema.SchemaType; |
46 | 47 | import org.apache.pulsar.reactive.client.adapter.AdaptedReactivePulsarClientFactory; |
47 | 48 | import org.apache.pulsar.reactive.client.api.MessageResult; |
| 49 | +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumer; |
| 50 | +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumerSpec; |
48 | 51 | import org.apache.pulsar.reactive.client.api.ReactivePulsarClient; |
49 | 52 | import org.junit.jupiter.api.Nested; |
50 | 53 | import org.junit.jupiter.api.Test; |
51 | 54 |
|
| 55 | +import org.springframework.beans.factory.ObjectProvider; |
52 | 56 | import org.springframework.beans.factory.annotation.Autowired; |
53 | 57 | import org.springframework.context.annotation.Bean; |
54 | 58 | import org.springframework.context.annotation.Configuration; |
|
71 | 75 | import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarListener; |
72 | 76 | import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarListenerMessageConsumerBuilderCustomizer; |
73 | 77 | import org.springframework.pulsar.reactive.core.DefaultReactivePulsarConsumerFactory; |
| 78 | +import org.springframework.pulsar.reactive.core.ReactiveMessageConsumerBuilderCustomizer; |
74 | 79 | import org.springframework.pulsar.reactive.core.ReactivePulsarConsumerFactory; |
| 80 | +import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.PulsarHeadersTest.PulsarListenerWithHeadersConfig; |
75 | 81 | import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SchemaCustomMappingsTestCases.SchemaCustomMappingsTestConfig.User2; |
| 82 | +import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SubscriptionTypeTests.WithDefaultType.WithDefaultTypeConfig; |
| 83 | +import org.springframework.pulsar.reactive.listener.ReactivePulsarListenerTests.SubscriptionTypeTests.WithSpecificTypes.WithSpecificTypesConfig; |
76 | 84 | import org.springframework.pulsar.support.PulsarHeaders; |
77 | 85 | import org.springframework.pulsar.test.support.PulsarTestContainerSupport; |
78 | 86 | import org.springframework.test.annotation.DirtiesContext; |
79 | 87 | import org.springframework.test.context.ContextConfiguration; |
80 | 88 | import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; |
| 89 | +import org.springframework.test.util.ReflectionTestUtils; |
| 90 | +import org.springframework.util.ObjectUtils; |
81 | 91 |
|
82 | 92 | import reactor.core.publisher.Flux; |
83 | 93 | import reactor.core.publisher.Mono; |
@@ -122,9 +132,14 @@ public PulsarTemplate<String> pulsarTemplate(PulsarProducerFactory<String> pulsa |
122 | 132 | return new PulsarTemplate<>(pulsarProducerFactory); |
123 | 133 | } |
124 | 134 |
|
| 135 | + @SuppressWarnings("unchecked") |
125 | 136 | @Bean |
126 | | - public ReactivePulsarConsumerFactory<String> pulsarConsumerFactory(ReactivePulsarClient pulsarClient) { |
127 | | - return new DefaultReactivePulsarConsumerFactory<>(pulsarClient, Collections.emptyList()); |
| 137 | + public ConsumerTrackingReactivePulsarConsumerFactory<String> pulsarConsumerFactory( |
| 138 | + ReactivePulsarClient pulsarClient, |
| 139 | + ObjectProvider<ReactiveMessageConsumerBuilderCustomizer<String>> defaultConsumerCustomizersProvider) { |
| 140 | + DefaultReactivePulsarConsumerFactory<String> consumerFactory = new DefaultReactivePulsarConsumerFactory<>( |
| 141 | + pulsarClient, defaultConsumerCustomizersProvider.orderedStream().toList()); |
| 142 | + return new ConsumerTrackingReactivePulsarConsumerFactory<>(consumerFactory); |
128 | 143 | } |
129 | 144 |
|
130 | 145 | @Bean |
@@ -721,7 +736,7 @@ Mono<Void> listenString(String ignored) { |
721 | 736 | } |
722 | 737 |
|
723 | 738 | @Nested |
724 | | - @ContextConfiguration(classes = ReactivePulsarListenerTests.PulsarHeadersTest.PulsarListenerWithHeadersConfig.class) |
| 739 | + @ContextConfiguration(classes = PulsarListenerWithHeadersConfig.class) |
725 | 740 | class PulsarHeadersTest { |
726 | 741 |
|
727 | 742 | static CountDownLatch simpleListenerLatch = new CountDownLatch(1); |
@@ -894,4 +909,160 @@ Mono<Void> listen2(String message) { |
894 | 909 |
|
895 | 910 | } |
896 | 911 |
|
| 912 | + @Nested |
| 913 | + class SubscriptionTypeTests { |
| 914 | + |
| 915 | + @Nested |
| 916 | + @ContextConfiguration(classes = WithDefaultTypeConfig.class) |
| 917 | + class WithDefaultType { |
| 918 | + |
| 919 | + static final CountDownLatch latchTypeNotSet = new CountDownLatch(1); |
| 920 | + |
| 921 | + @Test |
| 922 | + void whenTypeNotSetAnywhereThenFallbackTypeIsUsed( |
| 923 | + @Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception { |
| 924 | + assertThat(consumerFactory.topicNameToConsumerSpec).hasEntrySatisfying("rpl-typeNotSetAnywhere-topic", |
| 925 | + (consumerSpec) -> assertThat(consumerSpec.getSubscriptionType()) |
| 926 | + .isEqualTo(SubscriptionType.Exclusive)); |
| 927 | + pulsarTemplate.send("rpl-typeNotSetAnywhere-topic", "hello-rpl-typeNotSetAnywhere"); |
| 928 | + assertThat(latchTypeNotSet.await(10, TimeUnit.SECONDS)).isTrue(); |
| 929 | + } |
| 930 | + |
| 931 | + @Configuration(proxyBeanMethods = false) |
| 932 | + static class WithDefaultTypeConfig { |
| 933 | + |
| 934 | + @ReactivePulsarListener(topics = "rpl-typeNotSetAnywhere-topic", |
| 935 | + subscriptionName = "rpl-typeNotSetAnywhere-sub", |
| 936 | + consumerCustomizer = "subscriptionInitialPositionEarliest") |
| 937 | + Mono<Void> listenWithoutTypeSetAnywhere(String ignored) { |
| 938 | + latchTypeNotSet.countDown(); |
| 939 | + return Mono.empty(); |
| 940 | + } |
| 941 | + |
| 942 | + } |
| 943 | + |
| 944 | + } |
| 945 | + |
| 946 | + @Nested |
| 947 | + @ContextConfiguration(classes = WithSpecificTypesConfig.class) |
| 948 | + class WithSpecificTypes { |
| 949 | + |
| 950 | + static final CountDownLatch latchTypeSetConsumerFactory = new CountDownLatch(1); |
| 951 | + |
| 952 | + static final CountDownLatch latchTypeSetAnnotation = new CountDownLatch(1); |
| 953 | + |
| 954 | + static final CountDownLatch latchWithCustomizer = new CountDownLatch(1); |
| 955 | + |
| 956 | + @Test |
| 957 | + void whenTypeSetOnlyInConsumerFactoryThenConsumerFactoryTypeIsUsed( |
| 958 | + @Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception { |
| 959 | + assertThat(consumerFactory.getSpec("rpl-typeSetConsumerFactory-topic")) |
| 960 | + .extracting(ReactiveMessageConsumerSpec::getSubscriptionType) |
| 961 | + .isEqualTo(SubscriptionType.Shared); |
| 962 | + pulsarTemplate.send("rpl-typeSetConsumerFactory-topic", "hello-rpl-typeSetConsumerFactory"); |
| 963 | + assertThat(latchTypeSetConsumerFactory.await(10, TimeUnit.SECONDS)).isTrue(); |
| 964 | + } |
| 965 | + |
| 966 | + @Test |
| 967 | + void whenTypeSetOnAnnotationThenAnnotationTypeIsUsed( |
| 968 | + @Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception { |
| 969 | + assertThat(consumerFactory.getSpec("rpl-typeSetAnnotation-topic")) |
| 970 | + .extracting(ReactiveMessageConsumerSpec::getSubscriptionType) |
| 971 | + .isEqualTo(SubscriptionType.Key_Shared); |
| 972 | + pulsarTemplate.send("rpl-typeSetAnnotation-topic", "hello-rpl-typeSetAnnotation"); |
| 973 | + assertThat(latchTypeSetAnnotation.await(10, TimeUnit.SECONDS)).isTrue(); |
| 974 | + } |
| 975 | + |
| 976 | + @Test |
| 977 | + void whenTypeSetWithCustomizerThenCustomizerTypeIsUsed( |
| 978 | + @Autowired ConsumerTrackingReactivePulsarConsumerFactory<String> consumerFactory) throws Exception { |
| 979 | + assertThat(consumerFactory.getSpec("rpl-typeSetCustomizer-topic")) |
| 980 | + .extracting(ReactiveMessageConsumerSpec::getSubscriptionType) |
| 981 | + .isEqualTo(SubscriptionType.Failover); |
| 982 | + pulsarTemplate.send("rpl-typeSetCustomizer-topic", "hello-rpl-typeSetCustomizer"); |
| 983 | + assertThat(latchWithCustomizer.await(10, TimeUnit.SECONDS)).isTrue(); |
| 984 | + } |
| 985 | + |
| 986 | + @Configuration(proxyBeanMethods = false) |
| 987 | + static class WithSpecificTypesConfig { |
| 988 | + |
| 989 | + @Bean |
| 990 | + ReactiveMessageConsumerBuilderCustomizer<String> consumerFactoryDefaultSubTypeCustomizer() { |
| 991 | + return (b) -> b.subscriptionType(SubscriptionType.Shared); |
| 992 | + } |
| 993 | + |
| 994 | + @ReactivePulsarListener(topics = "rpl-typeSetConsumerFactory-topic", |
| 995 | + subscriptionName = "rpl-typeSetConsumerFactory-sub", subscriptionType = {}, |
| 996 | + consumerCustomizer = "subscriptionInitialPositionEarliest") |
| 997 | + Mono<Void> listenWithTypeSetOnlyOnConsumerFactory(String ignored) { |
| 998 | + latchTypeSetConsumerFactory.countDown(); |
| 999 | + return Mono.empty(); |
| 1000 | + } |
| 1001 | + |
| 1002 | + @ReactivePulsarListener(topics = "rpl-typeSetAnnotation-topic", |
| 1003 | + subscriptionName = "rpl-typeSetAnnotation-sub", subscriptionType = SubscriptionType.Key_Shared, |
| 1004 | + consumerCustomizer = "subscriptionInitialPositionEarliest") |
| 1005 | + Mono<Void> listenWithTypeSetOnAnnotation(String ignored) { |
| 1006 | + latchTypeSetAnnotation.countDown(); |
| 1007 | + return Mono.empty(); |
| 1008 | + } |
| 1009 | + |
| 1010 | + @ReactivePulsarListener(topics = "rpl-typeSetCustomizer-topic", |
| 1011 | + subscriptionName = "rpl-typeSetCustomizer-sub", subscriptionType = SubscriptionType.Key_Shared, |
| 1012 | + consumerCustomizer = "myCustomizer") |
| 1013 | + Mono<Void> listenWithTypeSetInCustomizer(String ignored) { |
| 1014 | + latchWithCustomizer.countDown(); |
| 1015 | + return Mono.empty(); |
| 1016 | + } |
| 1017 | + |
| 1018 | + @Bean |
| 1019 | + public ReactivePulsarListenerMessageConsumerBuilderCustomizer<String> myCustomizer() { |
| 1020 | + return cb -> cb.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest) |
| 1021 | + .subscriptionType(SubscriptionType.Failover); |
| 1022 | + } |
| 1023 | + |
| 1024 | + } |
| 1025 | + |
| 1026 | + } |
| 1027 | + |
| 1028 | + } |
| 1029 | + |
| 1030 | + static class ConsumerTrackingReactivePulsarConsumerFactory<T> implements ReactivePulsarConsumerFactory<T> { |
| 1031 | + |
| 1032 | + private Map<String, ReactiveMessageConsumerSpec> topicNameToConsumerSpec = new HashMap<>(); |
| 1033 | + |
| 1034 | + private ReactivePulsarConsumerFactory<T> delegate; |
| 1035 | + |
| 1036 | + ConsumerTrackingReactivePulsarConsumerFactory(ReactivePulsarConsumerFactory<T> delegate) { |
| 1037 | + this.delegate = delegate; |
| 1038 | + } |
| 1039 | + |
| 1040 | + @Override |
| 1041 | + public ReactiveMessageConsumer<T> createConsumer(Schema<T> schema) { |
| 1042 | + var consumer = this.delegate.createConsumer(schema); |
| 1043 | + storeSpec(consumer); |
| 1044 | + return consumer; |
| 1045 | + } |
| 1046 | + |
| 1047 | + @Override |
| 1048 | + public ReactiveMessageConsumer<T> createConsumer(Schema<T> schema, |
| 1049 | + List<ReactiveMessageConsumerBuilderCustomizer<T>> reactiveMessageConsumerBuilderCustomizers) { |
| 1050 | + var consumer = this.delegate.createConsumer(schema, reactiveMessageConsumerBuilderCustomizers); |
| 1051 | + storeSpec(consumer); |
| 1052 | + return consumer; |
| 1053 | + } |
| 1054 | + |
| 1055 | + private void storeSpec(ReactiveMessageConsumer<T> consumer) { |
| 1056 | + var consumerSpec = (ReactiveMessageConsumerSpec) ReflectionTestUtils.getField(consumer, "consumerSpec"); |
| 1057 | + var topicNamesKey = !ObjectUtils.isEmpty(consumerSpec.getTopicNames()) ? consumerSpec.getTopicNames().get(0) |
| 1058 | + : "no-topics-set"; |
| 1059 | + this.topicNameToConsumerSpec.put(topicNamesKey, consumerSpec); |
| 1060 | + } |
| 1061 | + |
| 1062 | + ReactiveMessageConsumerSpec getSpec(String topic) { |
| 1063 | + return this.topicNameToConsumerSpec.get(topic); |
| 1064 | + } |
| 1065 | + |
| 1066 | + } |
| 1067 | + |
897 | 1068 | } |
0 commit comments