Java tutorial
/* * Copyright 2010 Scott Fines * <p> * 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 * * 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 com.andyadc.menagerie; import org.apache.log4j.Logger; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * A Default implementation of a {@link ZkSessionManager}. * <p> * This implementation guarantees that all calls to {@code ConnectionListener}s will occur * on a separate, dedicated thread which is provided by a ThreadExecutorService. The default constructions * will create an ExecutorService for this, but the caller may specify a specific ExecutorService upon * construction. * * @author Scott Fines * @version 1.0 */ public class DefaultZkSessionManager implements ZkSessionManager { private static final Logger logger = Logger.getLogger(DefaultZkSessionManager.class); //this could potentially be a very write-heavy list, so a synchronized list will perform better //than a more traditional CopyOnWriteArrayList would be private List<ConnectionListener> listeners = Collections.synchronizedList(new ArrayList<ConnectionListener>()); private ZooKeeper zk; private final String connectionString; private final int timeout; private final ExecutorService executor; private volatile boolean shutdown; private ZkSessionPoller poller; private final int zkSessionPollInterval; /** * Creates a new instance of a DefaultZkSessionManager. * <p> * <p>This is equivalent to calling {@code new DefaultZkSessionManager(connectionString, timeout, -1)} * * @param connectionString the string to connect to ZooKeeper with (in the form of (serverIP):(port),...) * @param timeout the timeout to use before expiring the ZooKeeper session. */ public DefaultZkSessionManager(String connectionString, int timeout) { this(connectionString, timeout, Executors.newSingleThreadExecutor(), -1); } /** * Creates a new instance of a DefaultZkSessionManager, using the specified zkSessionPollInterval to * determine the polling interval to use for determining session expiration events. * <p> * <p>If {@code zkSessionPollInterval} is less than zero, then no polling will be executed. * * @param connectionString the string to connect to ZooKeeper with (in the form of (serverIP):(port),...) * @param timeout the timeout to use before ZooKeeper expires a session * @param zkSessionPollInterval the polling interval to use for manual session checking, or -1 if no * manual session checking is to be used */ public DefaultZkSessionManager(String connectionString, int timeout, int zkSessionPollInterval) { this(connectionString, timeout, Executors.newSingleThreadExecutor(), zkSessionPollInterval); } /** * Creates a new instance of a DefaultZkSessionManager, using the specified ExecutorService to handle * ConnectionListener api calls. * <p> * <p> * <p>This is equivalent to calling {@code new DefaultZkSessionManager(connectionString, timeout,executor, -1)} * * @param connectionString the string to connect to ZooKeeper with (in the form of (serverIP):(port),...) * @param timeout the timeout to use before expiring the ZooKeeper session. * @param executor the executor to use in constructing calling threads. */ public DefaultZkSessionManager(String connectionString, int timeout, ExecutorService executor) { this(connectionString, timeout, executor, -1); } /** * Creates a new instance of a DefaultZkSessionManager, using the specified zkSessionPollInterval to * determine the polling interval to use for determining session expiration events, and using the * specified ExecutorService to handle ConnectionListener api calls. * <p> * <p>If {@code zkSessionPollInterval} is less than zero, then no polling will be executed. * * @param connectionString the string to connect to ZooKeeper with (in the form of (serverIP):(port),...) * @param timeout the timeout to use before ZooKeeper expires a session * @param executor the executor to use in constructing calling threads * @param zkSessionPollInterval the polling interval to use for manual session checking, or -1 if no * manual session checking is to be used */ public DefaultZkSessionManager(String connectionString, int timeout, ExecutorService executor, int zkSessionPollInterval) { this.connectionString = connectionString; this.timeout = timeout; this.executor = executor; this.zkSessionPollInterval = zkSessionPollInterval; } @Override public synchronized ZooKeeper getZooKeeper() { if (shutdown) throw new IllegalStateException("Cannot request a ZooKeeper after the session has been closed!"); if (zk == null || zk.getState() == ZooKeeper.States.CLOSED) { try { zk = new ZooKeeper(connectionString, timeout, new SessionWatcher(this)); } catch (IOException e) { logger.error(e); throw new RuntimeException(e); } if (zkSessionPollInterval > 0) { //stop any previous polling, if it hasn't been stopped already if (poller != null) { poller.stopPolling(); } //create a new poller for this ZooKeeper instance poller = new ZkSessionPoller(zk, zkSessionPollInterval, new SessionPollListener(zk, this)); poller.startPolling(); } } else { //make sure that your zookeeper instance is synced zk.sync("/", new AsyncCallback.VoidCallback() { @Override public void processResult(int rc, String path, Object ctx) { // latch.countDown(); //do nothing, we're good } }, this); } return zk; } @Override public synchronized void closeSession() { try { if (zk != null) { try { zk.close(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } finally { executor.shutdown(); shutdown = true; } } @Override public void addConnectionListener(ConnectionListener listener) { listeners.add(listener); } @Override public void removeConnectionListener(ConnectionListener listener) { listeners.remove(listener); } /*--------------------------------------------------------------------------------------------------------------------*/ /*private helper methods */ private void notifyListeners(WatchedEvent event) { notifyState(event.getState()); } private void notifyState(final Watcher.Event.KeeperState state) { executor.submit(new Runnable() { @Override public void run() { if (state == Watcher.Event.KeeperState.Expired) { //tell everyone that all their watchers and ephemeral nodes have been removed--suck for (ConnectionListener listener : listeners) { listener.expired(); } zk = null; } else if (state == Watcher.Event.KeeperState.SyncConnected) { //tell everyone that we've reconnected to the Server, and they should make sure that their watchers //are in place for (ConnectionListener listener : listeners) { listener.syncConnected(); } } else if (state == Watcher.Event.KeeperState.Disconnected) { for (ConnectionListener listener : listeners) { listener.disconnected(); } } } }); } /* * Implementation to attach to the ZkSessionPoller for listening SPECIFICALLY for session expiration events * No other events are fired by the Sessionpoller, and no others need to be listened to. */ private static class SessionPollListener extends ConnectionListenerSkeleton { private final ZooKeeper zk; private final DefaultZkSessionManager sessionManager; private SessionPollListener(ZooKeeper zk, DefaultZkSessionManager sessionManager) { this.zk = zk; this.sessionManager = sessionManager; } @Override public void expired() { logger.info( "Session expiration has been detected. Notifying all connection listeners and cleaning up ZooKeeper State"); //notify applications sessionManager.notifyState(Watcher.Event.KeeperState.Expired); //shut down this ZooKeeper instance try { zk.close(); } catch (InterruptedException e) { logger.warn( "An InterruptedException was detected while attempting to close a ZooKeeper instance; ignoring because we're shutting it down anyway"); } } } private static class SessionWatcher implements Watcher { private final DefaultZkSessionManager manager; private SessionWatcher(DefaultZkSessionManager manager) { this.manager = manager; } @Override public void process(WatchedEvent event) { manager.notifyListeners(event); } } }