package edu.uw.zookeeper.protocol.server;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.zookeeper.KeeperException;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.util.concurrent.AbstractIdleService;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Service;
import edu.uw.zookeeper.ConfigurableTimeout;
import edu.uw.zookeeper.ZooKeeperApplication;
import edu.uw.zookeeper.common.AbstractActor;
import edu.uw.zookeeper.common.Automaton;
import edu.uw.zookeeper.common.RuntimeModule;
import edu.uw.zookeeper.common.TimeValue;
import edu.uw.zookeeper.net.Connection;
import edu.uw.zookeeper.net.ConnectionFactory;
import edu.uw.zookeeper.net.ServerConnectionFactory;
import edu.uw.zookeeper.protocol.ConnectMessage;
import edu.uw.zookeeper.protocol.FourLetterRequest;
import edu.uw.zookeeper.protocol.FourLetterResponse;
import edu.uw.zookeeper.protocol.Message;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.ProtocolState;
import edu.uw.zookeeper.protocol.Session;
import edu.uw.zookeeper.protocol.SessionListener;
import edu.uw.zookeeper.protocol.TelnetCloseRequest;
import edu.uw.zookeeper.protocol.TimeOutActor;
import edu.uw.zookeeper.protocol.TimeOutParameters;
import edu.uw.zookeeper.protocol.proto.IWatcherEvent;
import net.engio.mbassy.listener.Handler;
public class ServerConnectionsHandler<C extends ServerProtocolConnection<?,?>> extends AbstractIdleService implements ConnectionFactory.ConnectionsListener<C> {
public static Builder builder() {
return Builder.defaults();
}
public static class Builder implements ZooKeeperApplication.RuntimeBuilder<List<Service>, Builder> {
public static Builder defaults() {
return withConnectionBuilder(ServerConnectionFactoryBuilder.defaults());
}
public static Builder withConnectionBuilder(ServerConnectionFactoryBuilder connectionBuilder) {
return new Builder(connectionBuilder, null, null);
}
protected final ServerConnectionFactoryBuilder connectionBuilder;
protected final TimeValue timeOut;
protected final ServerExecutor<?> serverExecutor;
public Builder(
ServerConnectionFactoryBuilder connectionBuilder,
TimeValue timeOut,
ServerExecutor<?> serverExecutor) {
this.timeOut = timeOut;
this.connectionBuilder = checkNotNull(connectionBuilder);
this.serverExecutor = serverExecutor;
}
@Override
public RuntimeModule getRuntimeModule() {
return connectionBuilder.getRuntimeModule();
}
@Override
public Builder setRuntimeModule(RuntimeModule runtime) {
return setConnectionBuilder(connectionBuilder.setRuntimeModule(runtime));
}
public ServerConnectionFactoryBuilder getConnectionBuilder() {
return connectionBuilder;
}
public Builder setConnectionBuilder(ServerConnectionFactoryBuilder connectionBuilder) {
if (this.connectionBuilder == connectionBuilder) {
return this;
} else {
return newInstance(connectionBuilder, timeOut, serverExecutor);
}
}
public TimeValue getTimeOut() {
return timeOut;
}
public Builder setTimeOut(TimeValue timeOut) {
if (this.timeOut == timeOut) {
return this;
} else {
return newInstance(connectionBuilder, timeOut, serverExecutor);
}
}
public ServerExecutor<?> getServerExecutor() {
return serverExecutor;
}
public Builder setServerExecutor(ServerExecutor<?> serverExecutor) {
if (this.serverExecutor == serverExecutor) {
return this;
} else {
return newInstance(connectionBuilder, timeOut, serverExecutor);
}
}
@Override
public Builder setDefaults() {
checkNotNull(getServerExecutor());
if (getTimeOut() == null) {
return setTimeOut(getDefaultTimeOut()).setDefaults();
}
ServerConnectionFactoryBuilder connectionBuilder = getDefaultConnectionBuilder();
if (this.connectionBuilder != connectionBuilder) {
return setConnectionBuilder(connectionBuilder).setDefaults();
}
return this;
}
@Override
public List<Service> build() {
return setDefaults().doBuild();
}
protected Builder newInstance(
ServerConnectionFactoryBuilder connectionBuilder,
TimeValue timeOut,
ServerExecutor<?> serverExecutor) {
return new Builder(connectionBuilder, timeOut, serverExecutor);
}
protected List<Service> doBuild() {
final ServerConnectionFactory<? extends ServerProtocolConnection<?,?>> connections = connectionBuilder.build();
final ServerConnectionsHandler<ServerProtocolConnection<?,?>> handler = ServerConnectionsHandler.create(
getServerExecutor(),
getRuntimeModule().getExecutors().get(ScheduledExecutorService.class),
getTimeOut());
handler.addListener(
new Service.Listener() {
@Override
public void running() {
connections.subscribe(handler);
}
@Override
public void stopping(State from) {
connections.unsubscribe(handler);
}
}, MoreExecutors.directExecutor());
return Lists.<Service>newArrayList(
handler,
connections);
}
protected TimeValue getDefaultTimeOut() {
return ConfigurableTimeout.get(
getRuntimeModule().getConfiguration());
}
protected ServerConnectionFactoryBuilder getDefaultConnectionBuilder() {
return getConnectionBuilder().setDefaults();
}
}
public static <C extends ServerProtocolConnection<?,?>> ServerConnectionsHandler<C> create(
ServerExecutor<?> server,
ScheduledExecutorService scheduler,
TimeValue timeOut) {
// Assume Connection stores weak references to listeners
// so store strong references to handlers in map
ConcurrentMap<C, ServerConnectionsHandler<C>.ConnectionHandler<?>> handlers = new MapMaker().makeMap();
return new ServerConnectionsHandler<C>(server, scheduler, timeOut, handlers);
}
protected final Logger logger;
protected final TimeValue timeOut;
protected final ScheduledExecutorService scheduler;
protected final ServerExecutor<?> server;
protected final ConcurrentMap<C, ConnectionHandler<?>> handlers;
protected ServerConnectionsHandler(
ServerExecutor<?> server,
ScheduledExecutorService scheduler,
TimeValue timeOut,
ConcurrentMap<C, ConnectionHandler<?>> handlers) {
this.logger = LogManager.getLogger(this);
this.server = server;
this.scheduler = scheduler;
this.timeOut = timeOut;
this.handlers = handlers;
}
@Override
public void handleConnectionOpen(C connection) {
logger.debug("New connection {}", connection);
AnonymousConnectionHandler handler = new AnonymousConnectionHandler(connection);
switch (state()) {
case FAILED:
case STOPPING:
case TERMINATED:
handler.stop();
break;
default:
break;
}
}
@Override
protected Executor executor() {
return MoreExecutors.directExecutor();
}
@Override
protected void startUp() throws Exception {
}
@Override
protected void shutDown() throws Exception {
for (ConnectionHandler<?> handler: handlers.values()) {
handler.stop();
}
}
protected abstract class ConnectionHandler<I extends Message.Client> extends AbstractActor<I> implements Connection.Listener<Message.Client>, FutureCallback<Object> {
protected final C connection;
protected ConnectionHandler(C connection, Logger logger) {
super(logger);
this.connection = connection;
handlers.put(connection, this);
connection.subscribe(this);
if (connection.state() == Connection.State.CONNECTION_CLOSED) {
stop();
}
}
@Override
public void handleConnectionState(Automaton.Transition<Connection.State> event) {
if (Connection.State.CONNECTION_CLOSED == event.to()) {
stop();
}
}
@Override
public void onFailure(Throwable t) {
logger.debug("{}", this, t);
connection.close();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("connection", connection)
.toString();
}
@Override
protected void doRun() throws Exception {
}
@Override
protected void doStop() {
connection.unsubscribe(this);
handlers.remove(connection, this);
}
}
protected class AnonymousConnectionHandler extends ConnectionHandler<Message.Client> {
protected final TimeOutActor<Message.Client, Void> timer;
public AnonymousConnectionHandler(C connection) {
super(connection, LogManager.getLogger(AnonymousConnectionHandler.class));
this.timer = TimeOutActor.create(
TimeOutParameters.milliseconds(timeOut.value(TimeUnit.MILLISECONDS)), scheduler,
LogManager.getLogger(AnonymousConnectionHandler.class));
new TimeOutListener();
timer.run();
}
@Override
public void handleConnectionRead(Message.Client message) {
send(message);
}
@Override
public synchronized void onSuccess(Object result) {
if ((result != null) && (result instanceof Message.Server)) {
logger.debug("Sending {} ({})", result, this);
if (result instanceof FourLetterResponse) {
// it's possible that this response happens after
// the session connects
// in which case, we need to just drop it
if (handlers.get(connection) == this) {
connection.write((FourLetterResponse) result);
}
} else if (result instanceof ConnectMessage.Response) {
ConnectMessage.Response response = (ConnectMessage.Response) result;
if (response instanceof ConnectMessage.Response.Valid) {
SessionExecutor session = server.sessionExecutor(response.getSessionId());
// must subscribe new handler
// before sending the response
new SessionConnectionHandler(session, connection);
} else {
// if the response is Invalid, we want the response
// to be flushed to the client before closing the connection
// which ProtocolCodecConnection will do for us
}
// must unsubscribe before sending response
stop();
// this write will trigger reading messages again
connection.write(response);
} else {
throw new AssertionError(String.valueOf(result));
}
}
}
@Override
protected boolean doSend(Message.Client message) {
timer.send(message);
if (message instanceof FourLetterRequest) {
Futures.addCallback(server.anonymousExecutor().submit((FourLetterRequest) message), this);
} else if (message instanceof TelnetCloseRequest) {
connection.close();
} else if (message instanceof ConnectMessage.Request) {
// now we know that no more messages will be read
// by the connection until we write the response
Futures.addCallback(server.connectExecutor().submit((ConnectMessage.Request) message), this);
} else {
throw new AssertionError(String.valueOf(message));
}
return true;
}
@Override
protected void doStop() {
timer.cancel(false);
super.doStop();
}
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 SessionConnectionHandler extends ConnectionHandler<Message.ClientRequest<?>> implements SessionListener {
protected final SessionExecutor session;
public SessionConnectionHandler(
SessionExecutor session, C connection) {
super(connection, LogManager.getLogger(SessionConnectionHandler.class));
this.session = session;
session.subscribe(this);
}
@Override
public void handleConnectionRead(Message.Client message) {
send((Message.ClientRequest<?>) message);
}
@Override
public void handleAutomatonTransition(Automaton.Transition<ProtocolState> event) {
if (event.to() == ProtocolState.ERROR) {
onFailure(new KeeperException.SessionExpiredException());
}
}
@Override
public void handleNotification(
Operation.ProtocolResponse<IWatcherEvent> notification) {
onSuccess(notification);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("session", Session.toString(session.session().id()))
.add("connection", connection)
.toString();
}
@Handler
@Override
public void onSuccess(Object result) {
if ((result != null) && (result instanceof Message.Server)) {
logger.debug("Sending {} ({})", result, this);
connection.write((Message.Server) result);
}
}
@Override
protected boolean doSend(Message.ClientRequest<?> message) {
Futures.addCallback(session.submit(message), this);
return true;
}
@Override
protected void doStop() {
session.unsubscribe(this);
super.doStop();
}
}
}