Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.phantomthief.zookeeper.ZkBasedNodeResource Maven / Gradle / Ivy
package com.github.phantomthief.zookeeper;
import static com.github.phantomthief.util.MoreSuppliers.lazy;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.util.concurrent.Futures.addCallback;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static java.lang.Thread.MIN_PRIORITY;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import com.github.phantomthief.util.ThrowableBiConsumer;
import com.github.phantomthief.util.ThrowableBiFunction;
import com.github.phantomthief.util.ThrowableConsumer;
import com.github.phantomthief.util.ThrowableFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* @author w.vela
*/
public final class ZkBasedNodeResource implements Closeable {
private static final Logger logger = getLogger(ZkBasedNodeResource.class);
private static final int UNKNOWN = 0;
private static final int EXISTS = 1;
private static final int NOT_EXISTS = 2;
private final Object lock = new Object();
private final ThrowableBiFunction factory;
private final ThrowableBiFunction, Exception> refreshFactory;
private final Predicate cleanup;
private final long waitStopPeriod;
private final T emptyObject;
private final BiConsumer onResourceChange;
private final Supplier nodeCache;
private final Runnable nodeCacheShutdown;
private final BiConsumer factoryFailedListener;
@GuardedBy("lock")
private volatile T resource;
@GuardedBy("lock")
private volatile boolean emptyLogged = false;
@GuardedBy("lock")
private volatile boolean closed = false;
/**
* {@link #UNKNOWN}, {@link #EXISTS} or {@link #NOT_EXISTS}
*/
private volatile int zkNodeExists = UNKNOWN;
private volatile boolean hasAddListener = false;
private volatile Runnable nodeCacheRemoveListener;
private ZkBasedNodeResource(Builder builder) {
this.factory = builder.factory;
this.refreshFactory = builder.refreshFactory;
this.cleanup = builder.cleanup;
this.waitStopPeriod = builder.waitStopPeriod;
this.emptyObject = builder.emptyObject;
this.onResourceChange = builder.onResourceChange;
this.nodeCacheShutdown = builder.nodeCacheShutdown;
this.nodeCache = lazy(builder.cacheFactory);
this.factoryFailedListener = (d, t) -> {
for (ThrowableBiConsumer failedListener : builder.factoryFailedListeners) {
try {
failedListener.accept(d, t);
} catch (Throwable e) {
logger.error("", e);
}
}
};
}
/**
* use {@link #newGenericBuilder()} instead
*/
@Deprecated
@Nonnull
public static Builder newBuilder() {
return new Builder<>();
}
@CheckReturnValue
@Nonnull
public static GenericZkBasedNodeBuilder newGenericBuilder() {
return new GenericZkBasedNodeBuilder<>(newBuilder());
}
private static String path(NodeCache nodeCache) {
try {
if (nodeCache == null) {
return "n/a";
}
Field f = NodeCache.class.getDeclaredField("path");
f.setAccessible(true);
return (String) f.get(nodeCache);
} catch (Throwable e) {
logger.error("Ops.fail to get path from node:{}, exception:{}", nodeCache,
e.toString());
return null;
}
}
private static String zkConn(CuratorFramework zk) {
String result = zk.getZookeeperClient().getCurrentConnectionString();
if (StringUtils.isNotBlank(zk.getNamespace())) {
result += "[" + zk.getNamespace() + "]";
}
return result;
}
public ZkNode zkNode() {
T t = get();
return new ZkNode<>(t, zkNodeExists == EXISTS);
}
public T get() {
checkClosed();
if (resource == null) {
if (zkNodeExists == NOT_EXISTS) { // for performance, short circuit it outside sync block.
return emptyObject;
}
synchronized (lock) {
checkClosed();
if (resource == null) {
NodeCache cache = nodeCache.get();
tryAddListener(cache);
ChildData currentData = cache.getCurrentData();
if (currentData == null || currentData.getData() == null) {
zkNodeExists = NOT_EXISTS;
if (!emptyLogged) { // 只在刚开始一次或者几次打印这个log
logger.info("found no zk path for:{}:{}, using empty data:{}",
zkConn(cache.getClient()), path(cache), emptyObject);
emptyLogged = true;
}
return emptyObject;
}
zkNodeExists = EXISTS;
try {
resource = factory.apply(currentData.getData(), currentData.getStat());
if (onResourceChange != null) {
onResourceChange.accept(resource, emptyObject);
}
} catch (Exception e) {
factoryFailedListener.accept(currentData, e);
throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}
}
return resource;
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("zkNode has been closed.");
}
}
private void tryAddListener(NodeCache cache) {
if (!hasAddListener) {
NodeCacheListener nodeCacheListener = () -> {
T oldResource;
synchronized (lock) {
ChildData data = cache.getCurrentData();
oldResource = resource;
if (data != null && data.getData() != null) {
zkNodeExists = EXISTS;
ListenableFuture future = refreshFactory.apply(data.getData(),
data.getStat());
addCallback(future, new FutureCallback() {
@Override
public void onSuccess(@Nullable T result) {
resource = result;
cleanup(resource, oldResource, cache);
}
@Override
public void onFailure(Throwable t) {
factoryFailedListener.accept(data, t);
logger.error("", t);
}
}, directExecutor());
} else {
zkNodeExists = NOT_EXISTS;
resource = null;
emptyLogged = false;
cleanup(resource, oldResource, cache);
}
}
};
cache.getListenable().addListener(nodeCacheListener);
nodeCacheRemoveListener = () -> cache.getListenable().removeListener(nodeCacheListener);
hasAddListener = true;
}
}
private void cleanup(T currentResource, T oldResource, NodeCache nodeCache) {
if (oldResource != null && oldResource != emptyObject) {
if (currentResource == oldResource) {
logger.warn(
"[BUG!!!!] should NOT occurred, old resource is same as current, path:{}, {}",
path(nodeCache), oldResource);
} else {
new ThreadFactoryBuilder() //
.setNameFormat("old [" + oldResource.getClass().getSimpleName()
+ "] cleanup thread-[%d]")
.setUncaughtExceptionHandler(
(t, e) -> logger.error("fail to cleanup resource, path:{}, {}",
path(nodeCache), oldResource.getClass().getSimpleName(), e)) //
.setPriority(MIN_PRIORITY) //
.setDaemon(true) //
.build() //
.newThread(() -> {
do {
if (waitStopPeriod > 0) {
sleepUninterruptibly(waitStopPeriod, MILLISECONDS);
}
if (cleanup.test(oldResource)) {
break;
}
} while (true);
if (onResourceChange != null) {
onResourceChange.accept(currentResource, oldResource);
}
}).start();
return;
}
}
if (onResourceChange != null) {
onResourceChange.accept(currentResource, oldResource);
}
}
@Override
public void close() {
synchronized (lock) {
if (nodeCacheShutdown != null) {
nodeCacheShutdown.run();
}
if (nodeCacheRemoveListener != null) {
nodeCacheRemoveListener.run();
}
if (resource != null && resource != emptyObject && cleanup != null) {
cleanup.test(resource);
}
closed = true;
}
}
public boolean isClosed() {
return closed;
}
/**
* use {@link #newGenericBuilder()} instead
*/
@Deprecated
@SuppressWarnings({ "unchecked", "rawtypes" })
public static final class Builder {
private ThrowableBiFunction factory;
private ThrowableBiFunction, Exception> refreshFactory;
private Supplier cacheFactory;
private Predicate cleanup;
private long waitStopPeriod;
private E emptyObject;
private BiConsumer onResourceChange;
private Runnable nodeCacheShutdown;
private ListeningExecutorService refreshExecutor;
private List> factoryFailedListeners = new ArrayList<>();
@CheckReturnValue
@Nonnull
public Builder addFactoryFailedListener(
@Nonnull ThrowableConsumer listener) {
checkNotNull(listener);
return addFactoryFailedListener((d, t) -> listener.accept(t));
}
@CheckReturnValue
@Nonnull
public Builder addFactoryFailedListener(
@Nonnull ThrowableBiConsumer listener) {
factoryFailedListeners.add(checkNotNull(listener));
return (Builder) this;
}
/**
* use {@link #withFactoryEx}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder
withFactory(@Nonnull BiFunction factory) {
return withFactoryEx(factory::apply);
}
@CheckReturnValue
@Nonnull
public Builder withFactoryEx(
@Nonnull ThrowableBiFunction factory) {
Builder thisBuilder = (Builder) this;
thisBuilder.factory = (ThrowableBiFunction) factory;
return thisBuilder;
}
@CheckReturnValue
@Nonnull
public Builder withRefreshStringFactory(
@Nonnull ThrowableBiFunction factory) {
return withRefreshStringFactory(null, factory);
}
@CheckReturnValue
@Nonnull
public Builder withRefreshStringFactory(
@Nullable ListeningExecutorService executor,
@Nonnull ThrowableBiFunction factory) {
return withRefreshFactory(executor,
(b, s) -> factory.apply(b == null ? null : new String(b), s));
}
@CheckReturnValue
@Nonnull
public Builder withRefreshFactory(
@Nonnull ThrowableBiFunction factory) {
return withRefreshFactory(null, factory);
}
@CheckReturnValue
@Nonnull
public Builder withRefreshFactory(@Nullable ListeningExecutorService executor,
@Nonnull ThrowableBiFunction factory) {
Builder thisBuilder = (Builder) this;
if (executor == null) {
thisBuilder.refreshFactory = (b, s) -> {
try {
return immediateFuture(factory.apply(b, s));
} catch (Throwable t) {
return immediateFailedFuture(t);
}
};
} else {
thisBuilder.refreshFactory = (b, s) -> executor.submit(() -> factory.apply(b, s));
}
return thisBuilder;
}
@CheckReturnValue
@Nonnull
public Builder withAsyncRefreshStringFactory(
@Nonnull ThrowableBiFunction, Exception> factory) {
return withAsyncRefreshFactory(
(b, s) -> factory.apply(b == null ? null : new String(b), s));
}
@CheckReturnValue
@Nonnull
public Builder withAsyncRefreshFactory(
@Nonnull ThrowableBiFunction, Exception> factory) {
Builder thisBuilder = (Builder) this;
thisBuilder.refreshFactory = factory;
return thisBuilder;
}
@CheckReturnValue
@Nonnull
public Builder onResourceChange(BiConsumer super E1, ? super E1> callback) {
Builder thisBuilder = (Builder) this;
thisBuilder.onResourceChange = (BiConsumer) callback;
return thisBuilder;
}
/**
* use {@link #withFactoryEx}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder withFactory(@Nonnull Function factory) {
return withFactoryEx(factory::apply);
}
@CheckReturnValue
@Nonnull
public Builder
withFactoryEx(@Nonnull ThrowableFunction factory) {
return withFactoryEx((b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder withRefreshStringFactory(
@Nonnull ThrowableFunction factory) {
return withRefreshStringFactory(null, factory);
}
@CheckReturnValue
@Nonnull
public Builder withRefreshStringFactory(
@Nullable ListeningExecutorService executor,
@Nonnull ThrowableFunction factory) {
return withRefreshStringFactory(executor, (b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder withRefreshFactory(
@Nonnull ThrowableFunction factory) {
return withRefreshFactory(null, factory);
}
@CheckReturnValue
@Nonnull
public Builder withRefreshFactory(@Nullable ListeningExecutorService executor,
@Nonnull ThrowableFunction factory) {
return withRefreshFactory(executor, (b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder withAsyncRefreshStringFactory(
ThrowableFunction, Exception> factory) {
return withAsyncRefreshStringFactory((b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder withAsyncRefreshFactory(
ThrowableFunction, Exception> factory) {
return withAsyncRefreshFactory((b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder asyncRefresh(@Nonnull ListeningExecutorService executor) {
Builder thisBuilder = (Builder) this;
thisBuilder.refreshExecutor = checkNotNull(executor);
return thisBuilder;
}
/**
* use {@link #withStringFactoryEx}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder withStringFactory(BiFunction factory) {
return withStringFactoryEx(factory::apply);
}
@CheckReturnValue
@Nonnull
public Builder withStringFactoryEx(
ThrowableBiFunction factory) {
return withFactoryEx((b, s) -> factory.apply(b == null ? null : new String(b), s));
}
/**
* use {@link #withStringFactoryEx}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder withStringFactory(Function factory) {
return withStringFactoryEx(factory::apply);
}
@CheckReturnValue
@Nonnull
public Builder
withStringFactoryEx(ThrowableFunction factory) {
return withStringFactoryEx((b, s) -> factory.apply(b));
}
@CheckReturnValue
@Nonnull
public Builder withCacheFactory(Supplier cacheFactory) {
this.cacheFactory = cacheFactory;
return this;
}
@CheckReturnValue
@Nonnull
public Builder withCacheFactory(String path, CuratorFramework curator) {
return withCacheFactory(path, () -> curator);
}
@CheckReturnValue
@Nonnull
public Builder withCacheFactory(String path, Supplier curatorFactory) {
this.cacheFactory = () -> {
CuratorFramework thisClient = curatorFactory.get();
if (thisClient.getState() != CuratorFrameworkState.STARTED) {
thisClient.start();
}
NodeCache buildingCache = new NodeCache(thisClient, path);
try {
buildingCache.start();
// not safety check but do it better. due to IE breaks in rebuild.
if (Thread.currentThread().isInterrupted()) {
Thread.interrupted();
}
buildingCache.rebuild();
this.nodeCacheShutdown = () -> {
try {
buildingCache.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
return buildingCache;
} catch (Throwable e) {
throwIfUnchecked(e);
throw new RuntimeException(e);
}
};
return this;
}
/**
* using {@link #withCleanupConsumer(ThrowableConsumer)}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder withCleanup(ThrowableConsumer super E1, Throwable> cleanup) {
return withCleanupConsumer(cleanup);
}
@CheckReturnValue
@Nonnull
public Builder
withCleanupConsumer(ThrowableConsumer super E1, Throwable> cleanup) {
Builder thisBuilder = (Builder) this;
thisBuilder.cleanup = t -> {
try {
cleanup.accept(t);
return true;
} catch (Throwable e) {
logger.error("Ops. fail to close, path:{}", t, e);
return false;
}
};
return thisBuilder;
}
/**
* using {@link #withCleanupPredicate(Predicate)}
*/
@Deprecated
@CheckReturnValue
@Nonnull
public Builder withCleanup(Predicate super E1> cleanup) {
return withCleanupPredicate(cleanup);
}
@CheckReturnValue
@Nonnull
public Builder withCleanupPredicate(Predicate super E1> cleanup) {
Builder thisBuilder = (Builder) this;
thisBuilder.cleanup = (Predicate) cleanup;
return thisBuilder;
}
@CheckReturnValue
@Nonnull
public Builder withWaitStopPeriod(long waitStopPeriod) {
this.waitStopPeriod = waitStopPeriod;
return this;
}
@CheckReturnValue
@Nonnull
public Builder withEmptyObject(E1 emptyObject) {
Builder thisBuilder = (Builder) this;
thisBuilder.emptyObject = emptyObject;
return thisBuilder;
}
@Nonnull
public ZkBasedNodeResource build() {
ensure();
return new ZkBasedNodeResource(this);
}
private void ensure() {
checkNotNull(factory);
checkNotNull(cacheFactory);
if (refreshFactory == null) {
if (refreshExecutor != null) {
refreshFactory = (bs, stat) -> refreshExecutor
.submit(() -> factory.apply(bs, stat));
} else {
refreshFactory = (bs, stat) -> {
try {
return immediateFuture(factory.apply(bs, stat));
} catch (Throwable t) {
return immediateFailedFuture(t);
}
};
}
}
if (onResourceChange != null) {
BiConsumer temp = onResourceChange;
onResourceChange = (t, u) -> { // safe wrapper
try {
temp.accept(t, u);
} catch (Throwable e) {
logger.error("Ops.", e);
}
};
}
if (cleanup == null) {
withCleanup(t -> {
if (t instanceof Closeable) {
try {
((Closeable) t).close();
} catch (Throwable e) {
throw propagate(e);
}
}
});
}
}
}
}