package edu.uw.zookeeper.client; import java.util.concurrent.ExecutionException; import org.apache.zookeeper.KeeperException; import com.google.common.base.Optional; import com.google.common.util.concurrent.ForwardingListenableFuture; 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.Automaton; import edu.uw.zookeeper.common.FutureChain; import edu.uw.zookeeper.common.ChainedFutures; import edu.uw.zookeeper.common.Promise; import edu.uw.zookeeper.common.SettableFuturePromise; import edu.uw.zookeeper.common.ToStringListenableFuture.SimpleToStringListenableFuture; import edu.uw.zookeeper.net.Connection; import edu.uw.zookeeper.protocol.FourLetterRequest; import edu.uw.zookeeper.protocol.FourLetterResponse; import edu.uw.zookeeper.protocol.FourLetterWord; import edu.uw.zookeeper.protocol.Message; /** * Assumes that the server response is a single packet. */ public class FourLetterCommand extends ForwardingListenableFuture<String> implements ChainedFutures.ChainedProcessor<ListenableFuture<?>, FutureChain<ListenableFuture<?>>>, Connection.Listener<Message.ServerAnonymous>, Runnable { public static ListenableFuture<String> call( FourLetterWord word, ListenableFuture<? extends Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?>> connection) { final Promise<String> promise = SettableFuturePromise.create(); return ChainedFutures.run( ChainedFutures.<String>castLast( ChainedFutures.arrayList( new FourLetterCommand(word, connection, promise), 3)), promise); } public static ListenableFuture<String> callThenClose( final FourLetterWord word, final ListenableFuture<? extends Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?>> connection) { ListenableFuture<String> future = call(word, connection); future.addListener( CloseConnection.create(connection), MoreExecutors.directExecutor()); return future; } protected final FourLetterWord word; protected final ListenableFuture<? extends Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?>> connection; protected final Promise<String> response; protected FourLetterCommand( FourLetterWord word, ListenableFuture<? extends Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?>> connection, Promise<String> response) { this.word = word; this.connection = connection; this.response = response; addListener(this, MoreExecutors.directExecutor()); } @Override public Optional<? extends ListenableFuture<?>> apply( FutureChain<ListenableFuture<?>> input) throws Exception { if (input.isEmpty()) { return Optional.of(connection); } ListenableFuture<?> last = input.getLast(); if (last == connection) { Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?> connection; try { connection = this.connection.get(); } catch (ExecutionException e) { cancel(false); return Optional.absent(); } connection.subscribe(this); return Optional.of(connection.write(FourLetterRequest.forWord(word))); } else if (last == this) { return Optional.absent(); } else { try { last.get(); } catch (ExecutionException e) { cancel(false); return Optional.absent(); } return Optional.of(this); } } @Override public void handleConnectionState(Automaton.Transition<Connection.State> state) { switch (state.to()) { case CONNECTION_CLOSING: case CONNECTION_CLOSED: Futures.getUnchecked(connection).unsubscribe(this); if (!isDone()) { response.setException(new KeeperException.ConnectionLossException()); } break; default: break; } } @Override public void handleConnectionRead(Message.ServerAnonymous message) { Futures.getUnchecked(connection).unsubscribe(this); if (!isDone()) { response.set(((FourLetterResponse) message).stringValue()); } } @Override public void run() { if (isDone()) { if (connection.isDone()) { if (!connection.isCancelled()) { try { Connection<? super Message.ClientAnonymous,? extends Message.ServerAnonymous,?> connection = this.connection.get(); connection.unsubscribe(this); } catch (InterruptedException e) { throw new AssertionError(e); } catch (ExecutionException e) { } } } else { connection.cancel(false); } } } @Override protected ListenableFuture<String> delegate() { return response; } public static class CloseConnection<T extends Connection<?,?,?>> extends SimpleToStringListenableFuture<T> implements Runnable { public static <T extends Connection<?,?,?>> CloseConnection<T> create(ListenableFuture<T> future) { return new CloseConnection<T>(future); } protected CloseConnection(ListenableFuture<T> future) { super(future); } @Override public void run() { if (!isCancelled()) { try { Connection<?,?,?> c = get(); c.close(); } catch (InterruptedException e) { throw new AssertionError(e); } catch (ExecutionException e) { } } } } }