package edu.uw.zookeeper.server;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import com.google.common.base.MoreObjects;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import net.engio.mbassy.common.IConcurrentSet;
import edu.uw.zookeeper.common.Automaton;
import edu.uw.zookeeper.common.Automatons;
import edu.uw.zookeeper.protocol.Message;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.ProtocolRequestMessage;
import edu.uw.zookeeper.protocol.ProtocolState;
import edu.uw.zookeeper.protocol.Session;
import edu.uw.zookeeper.protocol.SessionListener;
import edu.uw.zookeeper.protocol.TimeOutActor;
import edu.uw.zookeeper.protocol.TimeOutParameters;
import edu.uw.zookeeper.protocol.proto.IWatcherEvent;
import edu.uw.zookeeper.protocol.proto.OpCode;
import edu.uw.zookeeper.protocol.proto.Records;
import edu.uw.zookeeper.protocol.server.SessionExecutor;
public abstract class AbstractSessionExecutor implements SessionExecutor, FutureCallback<Message.ServerResponse<?>>, SessionListener {
protected final Automatons.EventfulAutomaton<ProtocolState, Object> state;
protected final IConcurrentSet<SessionListener> listeners;
protected final Session session;
protected final TimeOutActor<Message.ClientSession, Void> timer;
protected AbstractSessionExecutor(
Session session,
Automatons.EventfulAutomaton<ProtocolState, Object> state,
IConcurrentSet<SessionListener> listeners,
ScheduledExecutorService scheduler) {
this.session = checkNotNull(session);
this.state = checkNotNull(state);
this.listeners = checkNotNull(listeners);
this.timer = TimeOutActor.create(
TimeOutParameters.milliseconds(session.parameters().timeOut().value(TimeUnit.MILLISECONDS)),
scheduler,
LogManager.getLogger(this));
new TimeOutListener();
state.subscribe(this);
timer.run();
}
public TimeOutActor<Message.ClientSession, Void> timer() {
return timer;
}
@Override
public ProtocolState state() {
return state.state();
}
@Override
public Session session() {
return session;
}
@Override
public ListenableFuture<Message.ServerResponse<?>> submit(Message.ClientRequest<?> request) {
timer.send(request);
state.apply(request);
ListenableFuture<Message.ServerResponse<?>> future = doSubmit(request);
new SubmitListener(future);
return future;
}
@Override
public void subscribe(SessionListener listener) {
listeners.add(listener);
}
@Override
public boolean unsubscribe(SessionListener listener) {
return listeners.remove(listener);
}
@Override
public void handleNotification(
Operation.ProtocolResponse<IWatcherEvent> notification) {
for (SessionListener listener: listeners) {
listener.handleNotification(notification);
}
}
@Override
public void handleAutomatonTransition(Automaton.Transition<ProtocolState> transition) {
for (SessionListener listener: listeners) {
listener.handleAutomatonTransition(transition);
}
switch (transition.to()) {
case ERROR:
// close session on error
Message.ClientRequest<Records.Request> request = ProtocolRequestMessage.of(0, Records.Requests.getInstance().get(OpCode.CLOSE_SESSION));
submit(request);
break;
case DISCONNECTED:
timer.cancel(false);
break;
default:
break;
}
}
@Override
public void onSuccess(Message.ServerResponse<?> result) {
try {
state.apply(result);
} catch (IllegalArgumentException e) {}
}
@Override
public void onFailure(Throwable t) {
state.apply(ProtocolState.ERROR);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("session", Session.toString(session().id())).toString();
}
protected abstract ListenableFuture<Message.ServerResponse<?>> doSubmit(Message.ClientRequest<?> request);
protected class TimeOutListener implements Runnable {
public TimeOutListener() {
timer.addListener(this, MoreExecutors.directExecutor());
}
@Override
public void run() {
if (timer.isDone()) {
if (!timer.isCancelled()) {
try {
timer.get();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
onFailure(e.getCause());
}
}
}
}
}
protected class SubmitListener implements Runnable {
private final ListenableFuture<Message.ServerResponse<?>> future;
public SubmitListener(ListenableFuture<Message.ServerResponse<?>> future) {
this.future = future;
future.addListener(this, MoreExecutors.directExecutor());
}
@Override
public void run() {
if (future.isDone()) {
if (!future.isCancelled()) {
Message.ServerResponse<?> result;
try {
result = future.get();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
onFailure(e.getCause());
return;
}
onSuccess(result);
}
}
}
}
}