|
114 | 114 | import org.apache.pulsar.broker.service.BrokerServiceException.NotAllowedException;
|
115 | 115 | import org.apache.pulsar.broker.service.BrokerServiceException.PersistenceException;
|
116 | 116 | import org.apache.pulsar.broker.service.BrokerServiceException.ServiceUnitNotReadyException;
|
| 117 | +import org.apache.pulsar.broker.service.TopicEventsListener.EventStage; |
| 118 | +import org.apache.pulsar.broker.service.TopicEventsListener.TopicEvent; |
117 | 119 | import org.apache.pulsar.broker.service.nonpersistent.NonPersistentTopic;
|
118 | 120 | import org.apache.pulsar.broker.service.persistent.DispatchRateLimiter;
|
119 | 121 | import org.apache.pulsar.broker.service.persistent.PersistentDispatcherMultipleConsumers;
|
@@ -287,6 +289,8 @@ public class BrokerService implements Closeable {
|
287 | 289 | private Set<BrokerEntryMetadataInterceptor> brokerEntryMetadataInterceptors;
|
288 | 290 | private Set<ManagedLedgerPayloadProcessor> brokerEntryPayloadProcessors;
|
289 | 291 |
|
| 292 | + private final TopicEventsDispatcher topicEventsDispatcher = new TopicEventsDispatcher(); |
| 293 | + |
290 | 294 | public BrokerService(PulsarService pulsar, EventLoopGroup eventLoopGroup) throws Exception {
|
291 | 295 | this.pulsar = pulsar;
|
292 | 296 | this.preciseTopicPublishRateLimitingEnable =
|
@@ -401,6 +405,16 @@ public BrokerService(PulsarService pulsar, EventLoopGroup eventLoopGroup) throws
|
401 | 405 | this.bundlesQuotas = new BundlesQuotas(pulsar.getLocalMetadataStore());
|
402 | 406 | }
|
403 | 407 |
|
| 408 | + public void addTopicEventListener(TopicEventsListener... listeners) { |
| 409 | + topicEventsDispatcher.addTopicEventListener(listeners); |
| 410 | + getTopics().keys().forEach(topic -> |
| 411 | + TopicEventsDispatcher.notify(listeners, topic, TopicEvent.LOAD, EventStage.SUCCESS, null)); |
| 412 | + } |
| 413 | + |
| 414 | + public void removeTopicEventListener(TopicEventsListener... listeners) { |
| 415 | + topicEventsDispatcher.removeTopicEventListener(listeners); |
| 416 | + } |
| 417 | + |
404 | 418 | // This call is used for starting additional protocol handlers
|
405 | 419 | public void startProtocolHandlers(
|
406 | 420 | Map<String, Map<InetSocketAddress, ChannelInitializer<SocketChannel>>> protocolHandlers) {
|
@@ -1010,17 +1024,37 @@ public CompletableFuture<Optional<Topic>> getTopic(final String topic, boolean c
|
1010 | 1024 | });
|
1011 | 1025 | } else {
|
1012 | 1026 | return topics.computeIfAbsent(topic, (name) -> {
|
| 1027 | + topicEventsDispatcher.notify(topicName.toString(), TopicEvent.LOAD, EventStage.BEFORE); |
1013 | 1028 | if (topicName.isPartitioned()) {
|
1014 | 1029 | final TopicName partitionedTopicName = TopicName.get(topicName.getPartitionedTopicName());
|
1015 | 1030 | return this.fetchPartitionedTopicMetadataAsync(partitionedTopicName).thenCompose((metadata) -> {
|
1016 | 1031 | if (topicName.getPartitionIndex() < metadata.partitions) {
|
1017 |
| - return createNonPersistentTopic(name); |
| 1032 | + topicEventsDispatcher |
| 1033 | + .notify(topicName.toString(), TopicEvent.CREATE, EventStage.BEFORE); |
| 1034 | + |
| 1035 | + CompletableFuture<Optional<Topic>> res = createNonPersistentTopic(name); |
| 1036 | + |
| 1037 | + CompletableFuture<Optional<Topic>> eventFuture = topicEventsDispatcher |
| 1038 | + .notifyOnCompletion(res, topicName.toString(), TopicEvent.CREATE); |
| 1039 | + topicEventsDispatcher |
| 1040 | + .notifyOnCompletion(eventFuture, topicName.toString(), TopicEvent.LOAD); |
| 1041 | + return res; |
1018 | 1042 | }
|
| 1043 | + topicEventsDispatcher.notify(topicName.toString(), TopicEvent.LOAD, EventStage.FAILURE); |
1019 | 1044 | return CompletableFuture.completedFuture(Optional.empty());
|
1020 | 1045 | });
|
1021 | 1046 | } else if (createIfMissing) {
|
1022 |
| - return createNonPersistentTopic(name); |
| 1047 | + topicEventsDispatcher.notify(topicName.toString(), TopicEvent.CREATE, EventStage.BEFORE); |
| 1048 | + |
| 1049 | + CompletableFuture<Optional<Topic>> res = createNonPersistentTopic(name); |
| 1050 | + |
| 1051 | + CompletableFuture<Optional<Topic>> eventFuture = topicEventsDispatcher |
| 1052 | + .notifyOnCompletion(res, topicName.toString(), TopicEvent.CREATE); |
| 1053 | + topicEventsDispatcher |
| 1054 | + .notifyOnCompletion(eventFuture, topicName.toString(), TopicEvent.LOAD); |
| 1055 | + return res; |
1023 | 1056 | } else {
|
| 1057 | + topicEventsDispatcher.notify(topicName.toString(), TopicEvent.LOAD, EventStage.FAILURE); |
1024 | 1058 | return CompletableFuture.completedFuture(Optional.empty());
|
1025 | 1059 | }
|
1026 | 1060 | });
|
@@ -1066,6 +1100,13 @@ public CompletableFuture<Void> deleteTopic(String topic, boolean forceDelete) {
|
1066 | 1100 | }
|
1067 | 1101 |
|
1068 | 1102 | public CompletableFuture<Void> deleteTopic(String topic, boolean forceDelete, boolean deleteSchema) {
|
| 1103 | + topicEventsDispatcher.notify(topic, TopicEvent.DELETE, EventStage.BEFORE); |
| 1104 | + CompletableFuture<Void> result = deleteTopicInternal(topic, forceDelete, deleteSchema); |
| 1105 | + topicEventsDispatcher.notifyOnCompletion(result, topic, TopicEvent.DELETE); |
| 1106 | + return result; |
| 1107 | + } |
| 1108 | + |
| 1109 | + public CompletableFuture<Void> deleteTopicInternal(String topic, boolean forceDelete, boolean deleteSchema) { |
1069 | 1110 | Optional<Topic> optTopic = getTopicReference(topic);
|
1070 | 1111 | if (optTopic.isPresent()) {
|
1071 | 1112 | Topic t = optTopic.get();
|
@@ -1526,6 +1567,24 @@ private void createPersistentTopic(final String topic, boolean createIfMissing,
|
1526 | 1567 | managedLedgerConfig.setCreateIfMissing(createIfMissing);
|
1527 | 1568 | managedLedgerConfig.setProperties(properties);
|
1528 | 1569 |
|
| 1570 | + topicEventsDispatcher.notify(topic, TopicEvent.LOAD, EventStage.BEFORE); |
| 1571 | + // load can fail with topicFuture completed non-exceptionally |
| 1572 | + // work around this |
| 1573 | + final CompletableFuture<Void> loadFuture = new CompletableFuture<>(); |
| 1574 | + topicFuture.whenComplete((res, ex) -> { |
| 1575 | + if (ex == null) { |
| 1576 | + loadFuture.complete(null); |
| 1577 | + } else { |
| 1578 | + loadFuture.completeExceptionally(ex); |
| 1579 | + } |
| 1580 | + }); |
| 1581 | + |
| 1582 | + if (createIfMissing) { |
| 1583 | + topicEventsDispatcher.notify(topic, TopicEvent.CREATE, EventStage.BEFORE); |
| 1584 | + topicEventsDispatcher.notifyOnCompletion(topicFuture, topic, TopicEvent.CREATE); |
| 1585 | + } |
| 1586 | + topicEventsDispatcher.notifyOnCompletion(loadFuture, topic, TopicEvent.LOAD); |
| 1587 | + |
1529 | 1588 | // Once we have the configuration, we can proceed with the async open operation
|
1530 | 1589 | managedLedgerFactory.asyncOpen(topicName.getPersistenceNamingEncoding(), managedLedgerConfig,
|
1531 | 1590 | new OpenLedgerCallback() {
|
@@ -1594,6 +1653,7 @@ public void openLedgerComplete(ManagedLedger ledger, Object ctx) {
|
1594 | 1653 | public void openLedgerFailed(ManagedLedgerException exception, Object ctx) {
|
1595 | 1654 | if (!createIfMissing && exception instanceof ManagedLedgerNotFoundException) {
|
1596 | 1655 | // We were just trying to load a topic and the topic doesn't exist
|
| 1656 | + loadFuture.completeExceptionally(exception); |
1597 | 1657 | topicFuture.complete(Optional.empty());
|
1598 | 1658 | } else {
|
1599 | 1659 | log.warn("Failed to create topic {}", topic, exception);
|
@@ -2085,6 +2145,8 @@ private void removeTopicFromCache(String topic, NamespaceBundle namespaceBundle,
|
2085 | 2145 | String bundleName = namespaceBundle.toString();
|
2086 | 2146 | String namespaceName = TopicName.get(topic).getNamespaceObject().toString();
|
2087 | 2147 |
|
| 2148 | + topicEventsDispatcher.notify(topic, TopicEvent.UNLOAD, EventStage.BEFORE); |
| 2149 | + |
2088 | 2150 | synchronized (multiLayerTopicsMap) {
|
2089 | 2151 | ConcurrentOpenHashMap<String, ConcurrentOpenHashMap<String, Topic>> namespaceMap = multiLayerTopicsMap
|
2090 | 2152 | .get(namespaceName);
|
@@ -2119,6 +2181,7 @@ private void removeTopicFromCache(String topic, NamespaceBundle namespaceBundle,
|
2119 | 2181 | if (compactor != null) {
|
2120 | 2182 | compactor.getStats().removeTopic(topic);
|
2121 | 2183 | }
|
| 2184 | + topicEventsDispatcher.notify(topic, TopicEvent.UNLOAD, EventStage.SUCCESS); |
2122 | 2185 | }
|
2123 | 2186 |
|
2124 | 2187 | public int getNumberOfNamespaceBundles() {
|
|
0 commit comments