/**
* Copyright 2016 Yahoo Inc.
*
* 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.yahoo.pulsar.zookeeper;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.yahoo.pulsar.zookeeper.ZooKeeperCache.CacheUpdater;
import com.yahoo.pulsar.zookeeper.ZooKeeperCache.Deserializer;
/**
* Provides a generic cache for reading objects stored in zookeeper.
*
* Maintains the objects already serialized in memory and sets watches to receive changes notifications.
*
* @param <T>
*/
public abstract class ZooKeeperDataCache<T> implements Deserializer<T>, CacheUpdater<T>, Watcher {
private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperDataCache.class);
private final ZooKeeperCache cache;
private final List<ZooKeeperCacheListener<T>> listeners = Lists.newCopyOnWriteArrayList();
private static final int FALSE = 0;
private static final int TRUE = 1;
private static final AtomicIntegerFieldUpdater<ZooKeeperDataCache> IS_SHUTDOWN_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(ZooKeeperDataCache.class, "isShutdown");
private volatile int isShutdown = FALSE;
public ZooKeeperDataCache(final ZooKeeperCache cache) {
this.cache = cache;
}
public CompletableFuture<Optional<T>> getAsync(String path) {
CompletableFuture<Optional<T>> future = new CompletableFuture<>();
cache.getDataAsync(path, this, this).thenAccept(entry -> {
future.complete(entry.map(Entry::getKey));
}).exceptionally(ex -> {
cache.asyncInvalidate(path);
future.completeExceptionally(ex);
return null;
});
return future;
}
/**
* Return an item from the cache
*
* If node doens't exist, the value will be not present.s
*
* @param path
* @return
* @throws Exception
*/
public Optional<T> get(final String path) throws Exception {
return getWithStat(path).map(Entry::getKey);
}
public Optional<Entry<T, Stat>> getWithStat(final String path) throws Exception {
return cache.getData(path, this, this);
}
/**
* Only for UTs (for now), as this clears the whole ZK data cache.
*/
public void clear() {
cache.invalidateAllData();
}
public void invalidate(final String path) {
cache.invalidateData(path);
}
@Override
public void reloadCache(final String path) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Reloading ZooKeeperDataCache at path {}", path);
}
cache.invalidate(path);
Optional<Entry<T, Stat>> cacheEntry = cache.getData(path, this, this);
if (!cacheEntry.isPresent()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Node [{}] does not exist", path);
}
return;
}
for (ZooKeeperCacheListener<T> listener : listeners) {
if (LOG.isDebugEnabled()) {
LOG.debug("Notifying listener {} at path {}", listener, path);
}
listener.onUpdate(path, cacheEntry.get().getKey(), cacheEntry.get().getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("Notified listener {} at path {}", listener, path);
}
}
} catch (Exception e) {
LOG.warn("Reloading ZooKeeperDataCache failed at path: {}", path, e);
}
}
@Override
public void registerListener(ZooKeeperCacheListener<T> listener) {
listeners.add(listener);
}
@Override
public void unregisterListener(ZooKeeperCacheListener<T> listener) {
listeners.remove(listener);
}
@Override
public void process(WatchedEvent event) {
LOG.info("[{}] Received ZooKeeper watch event: {}", cache.zkSession.get(), event);
if (IS_SHUTDOWN_UPDATER.get(this) == FALSE) {
cache.process(event, this);
}
}
public void close() {
IS_SHUTDOWN_UPDATER.set(this, TRUE);
}
}