Java tutorial
/* * Copyright 2012-2013 Continuuity,Inc. All Rights Reserved. * * 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.continuuity.weave.zookeeper; import com.continuuity.weave.common.Cancellable; import com.continuuity.weave.common.Threads; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicBoolean; /** * Collection of helper methods for common operations that usually needed when interacting with ZooKeeper. */ public final class ZKOperations { private static final Logger LOG = LoggerFactory.getLogger(ZKOperations.class); /** * Represents a ZK operation updates callback. * @param <T> Type of updated data. */ public interface Callback<T> { void updated(T data); } /** * Interface for defining callback method to receive node data updates. */ public interface DataCallback extends Callback<NodeData> { /** * Invoked when data of the node changed. * @param nodeData New data of the node, or {@code null} if the node has been deleted. */ @Override void updated(NodeData nodeData); } /** * Interface for defining callback method to receive children nodes updates. */ public interface ChildrenCallback extends Callback<NodeChildren> { @Override void updated(NodeChildren nodeChildren); } private interface Operation<T> { ZKClient getZKClient(); OperationFuture<T> exec(String path, Watcher watcher); } /** * Watch for data changes of the given path. The callback will be triggered whenever changes has been * detected. Note that the callback won't see every single changes, as that's not the guarantee of ZooKeeper. * If the node doesn't exists, it will watch for its creation then starts watching for data changes. * When the node is deleted afterwards, * * @param zkClient The {@link ZKClient} for the operation * @param path Path to watch * @param callback Callback to be invoked when data changes is detected. * @return A {@link Cancellable} to cancel the watch. */ public static Cancellable watchData(final ZKClient zkClient, final String path, final DataCallback callback) { final AtomicBoolean cancelled = new AtomicBoolean(false); watchChanges(new Operation<NodeData>() { @Override public ZKClient getZKClient() { return zkClient; } @Override public OperationFuture<NodeData> exec(String path, Watcher watcher) { return zkClient.getData(path, watcher); } }, path, callback, cancelled); return new Cancellable() { @Override public void cancel() { cancelled.set(true); } }; } public static ListenableFuture<String> watchDeleted(final ZKClient zkClient, final String path) { SettableFuture<String> completion = SettableFuture.create(); watchDeleted(zkClient, path, completion); return completion; } public static void watchDeleted(final ZKClient zkClient, final String path, final SettableFuture<String> completion) { Futures.addCallback(zkClient.exists(path, new Watcher() { @Override public void process(WatchedEvent event) { if (!completion.isDone()) { if (event.getType() == Event.EventType.NodeDeleted) { completion.set(path); } else { watchDeleted(zkClient, path, completion); } } } }), new FutureCallback<Stat>() { @Override public void onSuccess(Stat result) { if (result == null) { completion.set(path); } } @Override public void onFailure(Throwable t) { completion.setException(t); } }); } public static Cancellable watchChildren(final ZKClient zkClient, String path, ChildrenCallback callback) { final AtomicBoolean cancelled = new AtomicBoolean(false); watchChanges(new Operation<NodeChildren>() { @Override public ZKClient getZKClient() { return zkClient; } @Override public OperationFuture<NodeChildren> exec(String path, Watcher watcher) { return zkClient.getChildren(path, watcher); } }, path, callback, cancelled); return new Cancellable() { @Override public void cancel() { cancelled.set(true); } }; } /** * Watch for the given path until it exists. * @param zkClient The {@link ZKClient} to use. * @param path A ZooKeeper path to watch for existent. */ private static void watchExists(final ZKClient zkClient, final String path, final SettableFuture<String> completion) { Futures.addCallback(zkClient.exists(path, new Watcher() { @Override public void process(WatchedEvent event) { if (!completion.isDone()) { watchExists(zkClient, path, completion); } } }), new FutureCallback<Stat>() { @Override public void onSuccess(Stat result) { if (result != null) { completion.set(path); } } @Override public void onFailure(Throwable t) { completion.setException(t); } }); } private static <T> void watchChanges(final Operation<T> operation, final String path, final Callback<T> callback, final AtomicBoolean cancelled) { Futures.addCallback(operation.exec(path, new Watcher() { @Override public void process(WatchedEvent event) { if (!cancelled.get()) { watchChanges(operation, path, callback, cancelled); } } }), new FutureCallback<T>() { @Override public void onSuccess(T result) { if (!cancelled.get()) { callback.updated(result); } } @Override public void onFailure(Throwable t) { if (t instanceof KeeperException && ((KeeperException) t).code() == KeeperException.Code.NONODE) { final SettableFuture<String> existCompletion = SettableFuture.create(); existCompletion.addListener(new Runnable() { @Override public void run() { try { if (!cancelled.get()) { watchChanges(operation, existCompletion.get(), callback, cancelled); } } catch (Exception e) { LOG.error("Failed to watch children for path " + path, e); } } }, Threads.SAME_THREAD_EXECUTOR); watchExists(operation.getZKClient(), path, existCompletion); return; } LOG.error("Failed to watch data for path " + path + " " + t, t); } }); } private ZKOperations() { } }