package edu.uw.zookeeper.client;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.zookeeper.KeeperException;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.ForwardingListenableFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import edu.uw.zookeeper.common.CallablePromiseTask;
import edu.uw.zookeeper.common.Processor;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.common.SettableFuturePromise;
import edu.uw.zookeeper.data.AbsoluteZNodePath;
import edu.uw.zookeeper.data.AbstractNameTrie;
import edu.uw.zookeeper.data.DefaultsNode.AbstractDefaultsNode;
import edu.uw.zookeeper.data.NameTrie;
import edu.uw.zookeeper.data.NameTrie.Pointer;
import edu.uw.zookeeper.data.Operations;
import edu.uw.zookeeper.data.SimpleLabelTrie;
import edu.uw.zookeeper.data.ZNodeLabel;
import edu.uw.zookeeper.data.ZNodeName;
import edu.uw.zookeeper.data.ZNodePath;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.proto.Records;
public class DeleteSubtree extends ForwardingListenableFuture<AbsoluteZNodePath> implements Processor<Optional<? extends SubmittedRequest<Records.Request,?>>, Optional<DeleteSubtree>>, FutureCallback<DeleteSubtree.DeleteLeaf>, Runnable {
public static ListenableFuture<List<AbsoluteZNodePath>> deleteChildren(
final ZNodePath parent,
final ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client) {
return Futures.transform(
GetChildren.create(
parent,
client,
SettableFuturePromise.<List<AbsoluteZNodePath>>create()),
new AsyncFunction<List<AbsoluteZNodePath>, List<AbsoluteZNodePath>>() {
@Override
public ListenableFuture<List<AbsoluteZNodePath>> apply(
List<AbsoluteZNodePath> children) throws Exception {
ImmutableList.Builder<ListenableFuture<AbsoluteZNodePath>> deletes = ImmutableList.builder();
for (AbsoluteZNodePath child: children) {
deletes.add(DeleteSubtree.deleteAll(child, client));
}
return Futures.allAsList(deletes.build());
}
});
}
public static ListenableFuture<AbsoluteZNodePath> deleteAll(
AbsoluteZNodePath path,
ClientExecutor<? super Records.Request, ? extends Operation.ProtocolResponse<?>, ?> client) {
final DeleteSubtree delete = new DeleteSubtree(client, path, SettableFuturePromise.<AbsoluteZNodePath>create());
final ListenableFuture<Optional<DeleteSubtree>> fetcher = TreeWalker.forResult(delete)
.setClient(client)
.setResult(delete)
.setRequests(TreeWalker.toRequests(TreeWalker.parameters().setSync(true)))
.setRoot(path).build();
fetcher.addListener(
new Runnable() {
@Override
public void run() {
if (fetcher.isCancelled()) {
delete.cancel(false);
}
}
}, MoreExecutors.directExecutor());
return Futures.transform(
fetcher,
new AsyncFunction<Optional<DeleteSubtree>,AbsoluteZNodePath>() {
@Override
public ListenableFuture<AbsoluteZNodePath> apply(
Optional<DeleteSubtree> input) throws Exception {
return input.get();
}
});
}
protected final SimpleLabelTrie<Node> trie;
protected final PathToQuery<?,?> delete;
protected final ClientExecutor<? super Records.Request, ?, ?> client;
protected final AbsoluteZNodePath root;
protected final Set<DeleteLeaf> deletes;
protected final Promise<AbsoluteZNodePath> promise;
@SuppressWarnings("unchecked")
protected DeleteSubtree(
ClientExecutor<? super Records.Request, ?, ?> client,
AbsoluteZNodePath root,
Promise<AbsoluteZNodePath> promise) {
this.trie = SimpleLabelTrie.forRoot(Node.root());
this.client = client;
this.root = root;
this.delete = PathToQuery.forRequests(client, Operations.Requests.delete());
this.deletes = Sets.newHashSet();
this.promise = promise;
addListener(this, MoreExecutors.directExecutor());
}
@Override
public synchronized void run() {
if (isDone()) {
for (DeleteLeaf leaf: Iterables.consumingIterable(deletes)) {
leaf.cancel(false);
}
trie.clear();
}
}
@Override
public synchronized Optional<DeleteSubtree> apply(
Optional<? extends SubmittedRequest<Records.Request,?>> input)
throws Exception {
final Optional<DeleteSubtree> result;
if (input.isPresent()) {
final Records.Response response = input.get().get().record();
if (response instanceof Records.ChildrenGetter) {
final ZNodePath path = ZNodePath.fromString(((Records.PathGetter) input.get().getValue()).getPath());
final Node parent = Node.putIfAbsent(trie, path);
for (String child: ((Records.ChildrenGetter) response).getChildren()) {
parent.putIfAbsent(ZNodeLabel.fromString(child));
}
if (parent.isEmpty()) {
delete(parent);
}
}
result = Optional.absent();
} else {
result = Optional.of(this);
}
return result;
}
@Override
public synchronized void onSuccess(DeleteLeaf result) {
checkArgument(result.isDone());
deletes.remove(this);
if (!result.isCancelled()) {
try {
for (Operation.ProtocolResponse<?> response: result.get()) {
Operations.maybeError(response.record(), KeeperException.Code.NONODE);
}
} catch (Exception e) {
onFailure(e);
}
Node parent = result.node().parent().get();
if (result.node().remove() && (parent != null) && (parent.isEmpty())) {
if (parent.path().length() >= root.length()) {
delete(parent);
} else {
// done!
promise.set(root);
}
}
}
}
@Override
public void onFailure(Throwable t) {
if (!isDone()) {
promise.setException(t);
}
}
protected DeleteLeaf delete(Node node) {
DeleteLeaf listener = new DeleteLeaf(node, Futures.<Operation.ProtocolResponse<?>>allAsList(delete.apply(node.path()).call()));
deletes.add(listener);
listener.addListener(listener, MoreExecutors.directExecutor());
return listener;
}
@Override
protected ListenableFuture<AbsoluteZNodePath> delegate() {
return promise;
}
protected static class GetChildren implements Callable<Optional<List<AbsoluteZNodePath>>> {
public static ListenableFuture<List<AbsoluteZNodePath>> create(
ZNodePath path,
ClientExecutor<? super Records.Request,?,?> client,
Promise<List<AbsoluteZNodePath>> promise) {
SubmittedRequests<Records.Request,?> requests =
SubmittedRequests.submitRequests(
client,
Operations.Requests.sync().setPath(path).build(),
Operations.Requests.getChildren().setPath(path).build());
CallablePromiseTask<GetChildren,List<AbsoluteZNodePath>> task = CallablePromiseTask.create(new GetChildren(requests), promise);
requests.addListener(task, MoreExecutors.directExecutor());
return task;
}
protected final SubmittedRequests<Records.Request,?> requests;
public GetChildren(
SubmittedRequests<Records.Request,?> requests) {
this.requests = requests;
}
@Override
public Optional<List<AbsoluteZNodePath>> call() throws Exception {
if (requests.isDone()) {
List<? extends Operation.ProtocolResponse<?>> responses = requests.get();
ImmutableList.Builder<AbsoluteZNodePath> children = ImmutableList.builder();
for (int i=0; i<requests.getValue().size(); ++i) {
Operation.ProtocolResponse<?> response = responses.get(i);
if (!Operations.maybeError(response.record(), KeeperException.Code.NONODE).isPresent()) {
if (response.record() instanceof Records.ChildrenGetter) {
ZNodePath prefix = ZNodePath.fromString(((Records.PathGetter) requests.getValue().get(i)).getPath());
for (String child: ((Records.ChildrenGetter) response.record()).getChildren()) {
children.add((AbsoluteZNodePath) prefix.join(ZNodeLabel.fromString(child)));
}
}
}
}
return Optional.<List<AbsoluteZNodePath>>of(children.build());
} else {
return Optional.absent();
}
}
}
protected static final class Node extends AbstractDefaultsNode<Node> {
public static Node root() {
return new Node(AbstractNameTrie.<Node>rootPointer());
}
public static Node child(ZNodeName name, Node parent) {
NameTrie.Pointer<Node> childPointer = SimpleLabelTrie.weakPointer(name, parent);
return new Node(childPointer);
}
protected Node(Pointer<? extends Node> parent,
Map<ZNodeName, Node> children) {
super(parent, children);
}
protected Node(Pointer<? extends Node> parent) {
super(parent);
}
protected Node(ZNodePath path, Pointer<? extends Node> parent,
Map<ZNodeName, Node> children) {
super(path, parent, children);
}
@Override
protected Node newChild(ZNodeName label) {
return child(label, this);
}
}
protected final class DeleteLeaf extends ForwardingListenableFuture<List<Operation.ProtocolResponse<?>>> implements Runnable {
private final Node node;
private final ListenableFuture<List<Operation.ProtocolResponse<?>>> future;
public DeleteLeaf(Node node, ListenableFuture<List<Operation.ProtocolResponse<?>>> future) {
this.node = node;
this.future = future;
}
public Node node() {
return node;
}
@Override
public void run() {
if (isDone()) {
onSuccess(this);
}
}
@Override
protected ListenableFuture<List<Operation.ProtocolResponse<?>>> delegate() {
return future;
}
}
}