All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.ringleader.watcher.PersistentWatcher Maven / Gradle / Ivy

There is a newer version: 0.1.10
Show newest version
package com.hubspot.ringleader.watcher;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.listen.ListenerContainer;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Supplier;

public class PersistentWatcher implements Closeable {
  private static final Logger LOG = LoggerFactory.getLogger(PersistentWatcher.class);

  private final WatcherFactory parent;
  private final AtomicInteger lastVersion;
  private final AtomicBoolean started;
  private final AtomicBoolean closed;
  private final String path;
  private final ScheduledExecutorService executor;
  private final CuratorWatcher watcher;
  private final ListenerContainer listeners;

  PersistentWatcher(WatcherFactory parent,
                    final String path) {
    this.parent = parent;
    this.lastVersion = new AtomicInteger(-1);
    this.started = new AtomicBoolean();
    this.closed = new AtomicBoolean();
    this.path = path;
    this.executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {

      //@Override Java 5 compatibility
      public Thread newThread(Runnable r) {
        Thread thread = Executors.defaultThreadFactory().newThread(r);
        thread.setName("PersistentWatcher-" + path);
        thread.setDaemon(true);
        return thread;
      }
    });
    // keep a reference to the watcher so we don't add duplicates (Curator uses a set)
    this.watcher = new CuratorWatcher() {

      //@Override Java 5 compatibility
      public void process(WatchedEvent event) throws Exception {
        switch (event.getType()) {
          case NodeDeleted:
            lastVersion.set(-1);
            notifyListeners(Event.nodeDeleted());
            break;
          default:
            fetch(false);
        }
      }
    };
    this.listeners = new ListenerContainer();
  }


  public Supplier getCurator() {
    return new Supplier() {
      @Override
      public CuratorFramework get() {
        return parent.getCurator().get();
      }
    };
  }

  /**
   * Use {@link PersistentWatcher#getCurator()} instead
   *
   * Mutating this value will replace the curator framework for all
   * persistent watchers created by the parent factory of this watcher
   */
  @Deprecated
  public AtomicReference getCuratorReference() {
    return parent.getCurator();
  }

  public void start() {
    if (started.compareAndSet(false, true)) {
      executor.submit(new Runnable() {

        //@Override Java 5 compatibility
        public void run() {
          try {
            fetch(true);
          } finally {
            executor.schedule(this, 10, TimeUnit.MINUTES);
          }
        }
      });
    }
  }

  public Listenable getEventListenable() {
    return listeners;
  }

  //@Override Java 5 compatibility
  public void close() throws IOException {
    if (closed.compareAndSet(false, true)) {
      try {
        listeners.clear();
        lastVersion.set(-1);
        executor.shutdown();
      } finally {
        parent.recordClose();
      }
    }
  }

  private synchronized void fetch(final boolean backgroundFetch) {
    try {
      CuratorFramework curator = parent.getCurator().get();
      if (curator == null) {
        LOG.error("No curator present, replacing client");
        replaceCurator(backgroundFetch);
        return;
      }

      Stat stat = new Stat();
      byte[] data = curator.getData()
              .storingStatIn(stat)
              .usingWatcher(watcher)
              .forPath(path);

      int version = stat.getVersion();
      int previousVersion = lastVersion.getAndSet(version);
      if (version != previousVersion) {
        notifyListeners(Event.nodeUpdated(stat, data));

        if (previousVersion != -1 && backgroundFetch) {
          LOG.error("Watcher stopped firing, replacing client");
          replaceCurator(backgroundFetch);
        }
      }
    } catch (NoNodeException e) {
      LOG.debug("No node exists for path {}", path);
      if (lastVersion.getAndSet(-1) != -1) {
        notifyListeners(Event.nodeDeleted());
      }
    } catch (Exception e) {
      LOG.error("Error fetching data, replacing client", e);
      replaceCurator(backgroundFetch);
    }
  }

  private void notifyListeners(final Event event) {
    executor.submit(new Runnable() {

      //@Override Java 5 compatibility
      public void run() {
        listeners.forEach(new Function() {

          //@Override Java 5 compatibility
          public Void apply(EventListener listener) {
            listener.newEvent(event);
            return null;
          }
        });
      }
    });
  }

  private void replaceCurator(final boolean backgroundFetch) {
    parent.replaceCurator(new Runnable() {
      @Override
      public void run() {
        executor.submit(new Runnable() {
          @Override
          public void run() {
            fetch(backgroundFetch);
          }
        });
      }
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy