Java tutorial
/* Copyright (C) SYSTAP, LLC 2006-2008. All rights reserved. Contact: SYSTAP, LLC 4501 Tower Road Greensboro, NC 27410 licenses@bigdata.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jan 7, 2009 */ package com.bigdata.zookeeper; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.log4j.Logger; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; /** * An abstract implementation based on synchronized(this) and * {@link Object#notify()}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ abstract public class AbstractZNodeConditionWatcher implements Watcher { final static protected Logger log = Logger.getLogger(AbstractZNodeConditionWatcher.class); protected final ZooKeeper zookeeper; /** * * @param zookeeper * @param zpath * The path that is being watched. */ protected AbstractZNodeConditionWatcher(final ZooKeeper zookeeper, final String zpath) { if (zookeeper == null) throw new IllegalArgumentException(); if (zpath == null) throw new IllegalArgumentException(); this.zookeeper = zookeeper; this.zpath = zpath; } private volatile boolean disconnected = false; private volatile boolean conditionsatisfied = false; /** * The zpath that is being watched. */ protected final String zpath; /** * Return a representation of the watcher state (non-blocking). * <p> * Note: The implementation MUST be safe and non-blocking. */ public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append("{ zpath=" + zpath); sb.append(", conditionsatisfied=" + conditionsatisfied); sb.append(", disconnected=" + disconnected); toString(sb); // extension hook sb.append("}"); return sb.toString(); } /** * Subclasses may extend this method to add additional state into the * representation generated by {@link #toString()}. * <p> * Note: This implementation MUST be safe (no exceptions) and non-blocking. * * @param sb */ protected void toString(StringBuilder sb) { } /** * Clear the watch. This is necessary for the {@link Watcher} to stop * getting notices of changes after it has noticed the change that it was * looking for. */ final private void _clearWatches() { try { if (log.isInfoEnabled()) log.info("Clearing watch: " + this); clearWatch(); } catch (KeeperException ex) { // ignore log.warn(ex); } catch (InterruptedException ex) { // ignore. log.warn(ex); } } /** * Notify a {@link Thread} synchronized on itself when the znode that it is * watching generates an {@link WatchedEvent}. If the event is a * disconnect, then we instead set the {@link #disconnected} flag and return * immediately. */ public void process(final WatchedEvent event) { if (log.isInfoEnabled()) log.info(event.toString()); synchronized (this) { switch (event.getState()) { case Disconnected: // nothing to do until we are reconnected. disconnected = true; return; default: if (disconnected) { _resumeWatch(); } // fall through break; } boolean satisifed; try { satisifed = isConditionSatisfied(event); } catch (KeeperException e) { log.warn(this.toString(), e); return; } catch (InterruptedException e) { log.warn(this.toString(), e); return; } if (satisifed) { success(event.getType().toString()); return; } else { _resumeWatch(); } } } /** * Implementation must inspect the event and determine if the conditions are * satisfied. * * @param event * The {@link WatchedEvent} * * @return <code>true</code> if the event satisfied the condition. * * @throws KeeperException * @throws InterruptedException */ abstract protected boolean isConditionSatisfied(WatchedEvent event) throws KeeperException, InterruptedException; /** * Implementation must check the state of the znode using the {@link #zpath} * and determine if the conditions are satisfied <em>always</em> * resetting the watch(es) as a side-effect. * <p> * This is used to handle the initial case, where we need to know whether or * not the condition is satisfied before waiting for an event. * * @return * * @throws KeeperException * @throws InterruptedException */ abstract protected boolean isConditionSatisfied() throws KeeperException, InterruptedException; /** * Clear any watches. * * @throws KeeperException * @throws InterruptedException */ abstract protected void clearWatch() throws KeeperException, InterruptedException; /** * Resumes watching the zpath. However, if the condition is satisfied then * we report {@link #success(String)} and clear the watch. */ protected void _resumeWatch() { try { if (log.isInfoEnabled()) log.info("will reset watch"); // reset the watch. if (isConditionSatisfied()) { // in case we were disconnected. disconnected = false; // node already exists. success("already exists"); } // in case we were disconnected. disconnected = false; if (log.isInfoEnabled()) log.info("did reset watch"); } catch (Throwable t) { log.warn("Could not reset the watch: " + this, t); } } /** * Caller must be synchronized on <i>this</i>. */ protected void success(final String msg) { conditionsatisfied = true; if (log.isInfoEnabled()) log.info(msg + " : " + this); this.notify(); // clear watch or we will keep getting notices. _clearWatches(); } /** * This implementation always returns <code>false</code> but may be * overridden to permit cancellation of * {@link #awaitCondition(long, TimeUnit)}. * * @return */ protected boolean isCancelled() { return false; } /** * Wait up to timeout units for the watched znode to be created. * <p> * An instance of this watcher is set on a <strong>single</strong> znode. * The caller then {@link Object#wait()}s on the watcher until the watcher * notifies itself. When the caller wakes up it checks the time remaining * and whether or not the condition has been satisfied. If the timeout has * noticeably expired then it returns false. If the condition has been * satisfied and the timeout has not expired it returns true. Otherwise we * continue to wait. * <p> * The {@link Thread} MUST test {@link #conditionsatisfied} while holding * the lock and before waiting (in case the event has already occurred), and * again each time {@link Object#wait()} returns (since wait and friends MAY * return spuriously). The watch will be re-established until the timeout * has elapsed or the condition has been satisfied, at which point the * watch is explicitly cleared before returning to the caller. * <p> * This pattern should be robust in the face of a service disconnect. When a * reconnect {@link WatchedEvent} is received, it will test the condition * and then reset or clear its watch as necessary. * <p> * Note: the resolution is milliseconds at most. * * @param timeout * The timeout. * @param unit * The units. * * @return <code>false</code> if the waiting time detectably elapsed * before return from the method, else <code>true</code>. * * @throws TimeoutException * @throws InterruptedException */ public boolean awaitCondition(final long timeout, final TimeUnit unit) throws InterruptedException { return awaitCondition(true/* testConditionOnEntry */, timeout, unit); } /** * * @param testConditionOnEntry * @param timeout * @param unit * @return * @throws InterruptedException */ public boolean awaitCondition(final boolean testConditionOnEntry, final long timeout, final TimeUnit unit) throws InterruptedException { final long begin = System.currentTimeMillis(); long millis = unit.toMillis(timeout); synchronized (this) { if (testConditionOnEntry) { try { if (isConditionSatisfied()) { // condition was satisfied before waiting. success("on entry."); return true; } } catch (KeeperException ex) { log.warn("On entry: " + ex, ex); /* * Fall through. * * Note: by falling through we handle the case where the * client was not connected to a server when the caller made * their request or where a node does not yet exist, etc. */ } } while (millis > 0 && !conditionsatisfied && !isCancelled()) { this.wait(millis); millis -= (System.currentTimeMillis() - begin); if (log.isInfoEnabled()) log.info("woke up: conditionSatisifed=" + conditionsatisfied + ", remaining=" + millis + "ms"); } if (isCancelled()) { throw new InterruptedException(); } return millis > 0; } } }