package edu.uw.zookeeper.netty; import static com.google.common.base.Preconditions.checkNotNull; import org.apache.logging.log4j.Logger; import com.google.common.base.Optional; import edu.uw.zookeeper.common.Automatons; import edu.uw.zookeeper.common.Automatons.AutomatonListener; import edu.uw.zookeeper.common.Automaton; import edu.uw.zookeeper.common.Eventful; import edu.uw.zookeeper.common.Stateful; import edu.uw.zookeeper.net.Connection; import edu.uw.zookeeper.net.LoggingMarker; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; public class ConnectionStateHandler extends ChannelDuplexHandler implements Stateful<Connection.State>, Eventful<Automatons.AutomatonListener<Connection.State>> { public static Connection.State getChannelState(Channel channel) { Connection.State state = channel.isActive() ? Connection.State.CONNECTION_OPENED : (channel.isOpen() ? Connection.State.CONNECTION_OPENING : Connection.State.CONNECTION_CLOSED); return state; } public static ConnectionStateHandler fromPipeline(ChannelPipeline pipeline) { return (ConnectionStateHandler) pipeline.get(ConnectionStateHandler.class.getName()); } public static ChannelPipeline toPipeline( ConnectionStateHandler handler, ChannelPipeline pipeline) { return pipeline.addLast( ConnectionStateHandler.class.getName(), handler); } public static ConnectionStateHandler withLogger( Logger logger) { return new ConnectionStateHandler(newAutomaton(), logger); } public static ConnectionStateHandler newInstance( Automatons.EventfulAutomaton<Connection.State, Connection.State> state, Logger logger) { return new ConnectionStateHandler(state, logger); } public static Automatons.EventfulAutomaton<Connection.State, Connection.State> newAutomaton() { return Automatons.createSynchronizedEventful( Automatons.createEventful( Automatons.createSimple( Connection.State.CONNECTION_OPENING))); } private final Logger logger; private final Automatons.EventfulAutomaton<Connection.State, Connection.State> automaton; protected ConnectionStateHandler( Automatons.EventfulAutomaton<Connection.State, Connection.State> automaton, Logger logger) { this.logger = checkNotNull(logger); this.automaton = checkNotNull(automaton); } @Override public Connection.State state() { return automaton.state(); } @Override public void subscribe(AutomatonListener<Connection.State> listener) { automaton.subscribe(listener); } @Override public boolean unsubscribe(AutomatonListener<Connection.State> listener) { return automaton.unsubscribe(listener); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { apply(ctx, getChannelState(ctx.channel())); super.handlerAdded(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { apply(ctx, Connection.State.CONNECTION_OPENING); apply(ctx, Connection.State.CONNECTION_OPENED); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { apply(ctx, Connection.State.CONNECTION_CLOSING); apply(ctx, Connection.State.CONNECTION_CLOSED); super.channelInactive(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.warn(LoggingMarker.NET_MARKER.get(), "EXCEPTION {}", ctx.channel(), cause); ctx.close(); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception { apply(ctx, Connection.State.CONNECTION_CLOSING); super.close(ctx, future); } protected Optional<Automaton.Transition<Connection.State>> apply(ChannelHandlerContext ctx, Connection.State input) { Optional<Automaton.Transition<Connection.State>> state = automaton.apply(input); if (logger.isTraceEnabled() && state.isPresent()) { logger.trace(LoggingMarker.NET_MARKER.get(), "STATE {} ({})", state.get(), ctx.channel()); } return state; } }