/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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 io.jafka.consumer;
import com.github.zkclient.IZkChildListener;
import com.github.zkclient.IZkStateListener;
import com.github.zkclient.ZkClient;
import io.jafka.common.ConsumerRebalanceFailedException;
import io.jafka.server.ServerStartable;
import io.jafka.utils.zookeeper.ZkUtils;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
/**
* @author adyliu (imxylz@gmail.com)
* @since 1.0
*/
public class ZookeeperTopicEventWatcher implements Closeable {
private final TopicEventHandler<String> eventHandler;
private final ServerStartable serverStartable;
private final Object lock = new Object();
private ZkClient zkClient;
private static final Logger logger = LoggerFactory.getLogger(ZookeeperTopicEventWatcher.class);
public ZookeeperTopicEventWatcher(ConsumerConfig consumerConfig, TopicEventHandler<String> eventHandler, ServerStartable serverStartable) {
super();
this.eventHandler = eventHandler;
this.serverStartable = serverStartable;
//
this.zkClient = new ZkClient(consumerConfig.getZkConnect(), //
consumerConfig.getZkSessionTimeoutMs(), //
consumerConfig.getZkConnectionTimeoutMs());
startWatchingTopicEvents();
}
private void startWatchingTopicEvents() {
ZkTopicEventListener topicEventListener = new ZkTopicEventListener();
ZkUtils.makeSurePersistentPathExists(zkClient, ZkUtils.BrokerTopicsPath);
zkClient.subscribeStateChanges(new ZkSessionExpireListener(topicEventListener));
List<String> topics = zkClient.subscribeChildChanges(ZkUtils.BrokerTopicsPath, topicEventListener);
//
// call to bootstrap topic list
try {
topicEventListener.handleChildChange(ZkUtils.BrokerTopicsPath, topics);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void stopWatchingTopicEvents() {
this.zkClient.unsubscribeAll();
}
public void close() throws IOException {
synchronized (lock) {
if (zkClient == null) {
logger.warn("cannot shutdown already shutdown topic event watcher.");
return;
}
stopWatchingTopicEvents();
zkClient.close();
zkClient = null;
}
}
class ZkTopicEventListener implements IZkChildListener {
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
synchronized (lock) {
if (zkClient == null)
return;
try {
List<String> latestTopics = zkClient.getChildren(ZkUtils.BrokerTopicsPath);
logger.debug("all Topics: " + latestTopics);
eventHandler.handleTopicEvent(latestTopics);
} catch (ConsumerRebalanceFailedException e) {
logger.error("can't rebalance in embedded consumer); proceed to shutdown", e);
serverStartable.close();
} catch (Exception e) {
logger.error("error in handling child changes in embedded consumer", e);
}
}
}
}
class ZkSessionExpireListener implements IZkStateListener {
private final ZkTopicEventListener zkTopicEventListener;
public ZkSessionExpireListener(ZkTopicEventListener zkTopicEventListener) {
this.zkTopicEventListener = zkTopicEventListener;
}
public void handleNewSession() throws Exception {
synchronized (lock) {
if (zkClient != null) {
zkClient.subscribeChildChanges(ZkUtils.BrokerTopicsPath, zkTopicEventListener);
}
}
}
public void handleStateChanged(KeeperState state) throws Exception {
}
}
}