package edu.uw.zookeeper.client;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.logging.log4j.LogManager;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import edu.uw.zookeeper.common.AbstractActor;
import edu.uw.zookeeper.common.Processor;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.common.SettableFuturePromise;
import edu.uw.zookeeper.common.ToStringListenableFuture;
import edu.uw.zookeeper.data.AbsoluteZNodePath;
import edu.uw.zookeeper.data.RootZNodePath;
import edu.uw.zookeeper.data.ZNodePath;
import edu.uw.zookeeper.data.Operations;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.proto.Records;
public class TreeWalker<V> extends AbstractActor<ZNodePath> implements ListenableFuture<Optional<V>> {
public static <V> Builder<V> builder() {
return Builder.defaults();
}
public static <V> Builder<V> forResult(Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<V>> result) {
return Builder.<V>defaults().setResult(result);
}
public static Parameters parameters() {
return Parameters.defaults();
}
public static PathToRequests toRequests(Parameters parameters) {
ImmutableList.Builder<Operations.Builder<? extends Records.Request>> builders = ImmutableList.builder();
if (parameters.getSync()) {
builders.add(Operations.Requests.sync());
}
if (parameters.getData()) {
builders.add(
Operations.Requests.getData().setWatch(parameters.getWatch()));
}
if (parameters.getAcl()) {
builders.add(
Operations.Requests.getAcl());
}
builders.add(
Operations.Requests.getChildren()
.setWatch(parameters.getWatch()).setStat(parameters.getStat()));
return PathToRequests.forIterable(builders.build());
}
public static <V> Processor<Object, Optional<V>> noResult() {
return new Processor<Object, Optional<V>>() {
@Override
public Optional<V> apply(Object input) {
return Optional.absent();
}
};
}
public static final class Parameters {
public static Parameters defaults() {
return valueOf(true, false, false, false, false);
}
public static Parameters valueOf(boolean sync, boolean watch, boolean getData, boolean getAcl,
boolean getStat) {
return new Parameters(sync, watch, getData, getAcl, getStat);
}
private final boolean sync;
private final boolean watch;
private final boolean getData;
private final boolean getAcl;
private final boolean getStat;
protected Parameters(boolean sync, boolean watch, boolean getData, boolean getAcl,
boolean getStat) {
this.sync = sync;
this.watch = watch;
this.getData = getData;
this.getAcl = getAcl;
this.getStat = getStat;
}
public boolean getSync() {
return sync;
}
public Parameters setSync(boolean sync) {
return valueOf(sync, watch, getData, getAcl, getStat);
}
public boolean getWatch() {
return watch;
}
public Parameters setWatch(boolean watch) {
return valueOf(sync, watch, getData, getAcl, getStat);
}
public boolean getData() {
return getData;
}
public Parameters setData(boolean getData) {
return valueOf(sync, watch, getData, getAcl, getStat);
}
public boolean getAcl() {
return getAcl;
}
public Parameters setAcl(boolean getAcl) {
return valueOf(sync, watch, getData, getAcl, getStat);
}
public boolean getStat() {
return getStat;
}
public Parameters setStat(boolean getStat) {
return valueOf(sync, watch, getData, getAcl, getStat);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sync", sync)
.add("watch", watch)
.add("getData", getData)
.add("getAcl", getAcl)
.add("getStat", getStat).toString();
}
}
public static final class Builder<V> implements edu.uw.zookeeper.common.Builder<TreeWalker<V>> {
public static <V> Builder<V> defaults() {
return new Builder<V>(ZNodePath.root(), null, null, null, null, null);
}
private final ZNodePath root;
private final PathToRequests requests;
private final Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<V>> result;
private final Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> iterator;
private final ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client;
private final Executor executor;
protected Builder(
ZNodePath root,
PathToRequests requests,
Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> iterator,
Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<V>> result,
ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client,
Executor executor) {
this.root = root;
this.requests = requests;
this.iterator = iterator;
this.result = result;
this.client = client;
this.executor = executor;
}
public ZNodePath getRoot() {
return root;
}
public Builder<V> setRoot(ZNodePath root) {
return newInstance(root, requests, iterator, result, client, executor);
}
public PathToRequests getRequests() {
return requests;
}
public Builder<V> setRequests(PathToRequests requests) {
return newInstance(root, requests, iterator, result, client, executor);
}
public Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> getIterator() {
return iterator;
}
public Builder<V> setIterator(Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> iterator) {
return newInstance(root, requests, iterator, result, client, executor);
}
public Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<V>> getResult() {
return result;
}
public <U> Builder<U> setResult(Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<U>> result) {
return newInstance(root, requests, iterator, result, client, executor);
}
public ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> getClient() {
return client;
}
public Builder<V> setClient(ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client) {
return newInstance(root, requests, iterator, result, client, executor);
}
public Executor getExecutor() {
return executor;
}
public Builder<V> setExecutor(Executor executor) {
return newInstance(root, requests, iterator, result, client, executor);
}
@Override
public TreeWalker<V> build() {
return setDefaults().doBuild();
}
public Builder<V> setDefaults() {
checkState(client != null);
if (root == null) {
return setRoot(getDefaultRoot()).setDefaults();
}
if (requests == null) {
return setRequests(getDefaultRequests()).setDefaults();
}
if (iterator == null) {
return setIterator(getDefaultIterator()).setDefaults();
}
if (result == null) {
return setResult(getDefaultResult()).setDefaults();
}
if (executor == null) {
return setExecutor(getDefaultExecutor()).setDefaults();
}
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("root", getRoot())
.add("requests", getRequests())
.add("iterator", getIterator())
.add("result", getResult())
.add("client", getClient())
.add("executor", getExecutor()).toString();
}
protected TreeWalker<V> doBuild() {
Promise<Optional<V>> promise = SettableFuturePromise.<Optional<V>>create();
TreeWalker<V> actor = new TreeWalker<V>(this, promise);
actor.send(getRoot());
return actor;
}
protected <U> Builder<U> newInstance(
ZNodePath root,
PathToRequests requests,
Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> iterator,
Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<U>> result,
ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client,
Executor executor) {
return new Builder<U>(root, requests, iterator, result, client, executor);
}
protected ZNodePath getDefaultRoot() {
return RootZNodePath.getInstance();
}
protected PathToRequests getDefaultRequests() {
return toRequests(parameters());
}
protected Processor<? super SubmittedRequest<Records.Request,?>, ? extends Iterator<? extends ZNodePath>> getDefaultIterator() {
return GetChildrenIterator.create();
}
protected Processor<? super Optional<? extends SubmittedRequest<Records.Request,?>>, ? extends Optional<V>> getDefaultResult() {
return TreeWalker.<V>noResult();
}
protected Executor getDefaultExecutor() {
return MoreExecutors.directExecutor();
}
}
public static final class GetChildrenIterator implements Processor<SubmittedRequest<Records.Request,?>, Iterator<AbsoluteZNodePath>> {
public static GetChildrenIterator create() {
return new GetChildrenIterator();
}
protected GetChildrenIterator() {}
@Override
public Iterator<AbsoluteZNodePath> apply(SubmittedRequest<Records.Request,?> input) throws InterruptedException, ExecutionException {
final Records.Response response = input.get().record();
if (response instanceof Records.ChildrenGetter) {
final ZNodePath path = ZNodePath.fromString(((Records.PathGetter) input.getValue()).getPath());
return Iterators.transform(
((Records.ChildrenGetter) response).getChildren().iterator(),
ChildToPath.forParent(path));
} else {
return ImmutableSet.<AbsoluteZNodePath>of().iterator();
}
}
}
// not thread safe
protected final Builder<V> builder;
protected final Queue<SubmittedRequest<Records.Request,?>> pending;
protected final Promise<Optional<V>> future;
protected TreeWalker(
Builder<V> builder,
Promise<Optional<V>> promise) {
super(LogManager.getLogger(TreeWalker.class));
this.builder = checkNotNull(builder);
this.future = promise;
this.pending = Queues.newArrayDeque();
addListener(this, builder.getExecutor());
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
@Override
public Optional<V> get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public Optional<V> get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
@Override
public boolean isCancelled() {
return future.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public void addListener(Runnable listener, Executor executor) {
future.addListener(listener, executor);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(builder.getRoot()).addValue(ToStringListenableFuture.toString(future)).toString();
}
@Override
protected synchronized boolean doSend(ZNodePath message) {
List<Records.Request> requests = builder.getRequests().apply(message);
List<SubmittedRequest<Records.Request,?>> submits = Lists.newArrayListWithCapacity(requests.size());
for (Records.Request request: requests) {
SubmittedRequest<Records.Request,?> submitted = SubmittedRequest.submit(builder.getClient(), request);
submits.add(submitted);
pending.add(submitted);
if (state() == State.TERMINATED) {
for (SubmittedRequest<Records.Request,?> e: submits) {
e.cancel(false);
pending.remove(e);
}
return false;
}
}
for (SubmittedRequest<Records.Request,?> submitted: submits) {
submitted.addListener(new RequestListener(submitted), builder.getExecutor());
}
return true;
}
@Override
protected synchronized void doRun() throws Exception {
if (isDone() || pending.isEmpty()) {
// We're done!
stop();
}
}
@Override
protected synchronized void doStop() {
Iterator<? extends Future<?>> itr = Iterators.consumingIterator(pending.iterator());
while (itr.hasNext()) {
itr.next().cancel(true);
}
try {
future.set(builder.getResult().apply(Optional.<SubmittedRequest<Records.Request,?>>absent()));
} catch (Exception e) {
future.setException(e);
}
}
protected synchronized void handleRequest(SubmittedRequest<Records.Request,?> request) {
assert (request.isDone());
pending.remove(request);
if (!isDone() && (state() != State.TERMINATED)) {
try {
Optional<V> value = builder.getResult().apply(Optional.of(request));
if (value.isPresent()) {
future.set(value);
} else {
Iterator<? extends ZNodePath> paths = builder.getIterator().apply(request);
while (paths.hasNext()) {
send(paths.next());
}
}
} catch (Exception e) {
future.setException(e);
}
run();
}
}
protected class RequestListener implements Runnable {
protected final SubmittedRequest<Records.Request,?> future;
public RequestListener(SubmittedRequest<Records.Request,?> future) {
this.future = future;
}
@Override
public void run() {
if (future.isDone()) {
handleRequest(future);
}
}
}
}