package net.jxta.impl.endpoint.netty; import java.io.IOException; import java.net.ConnectException; import java.nio.channels.ClosedChannelException; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import net.jxta.endpoint.EndpointAddress; import net.jxta.endpoint.Message; import net.jxta.impl.endpoint.msgframing.WelcomeMessage; import net.jxta.logging.Logging; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; /** * The terminal upstream listener, which takes decoded messages (either WelcomeMessage or Message) * and passes this information up to the MessageArrivalListener - typically, the NettyMessenger * for this connection. * * @author iain.mcginniss@onedrum.com */ public class MessageDispatchHandler extends SimpleChannelUpstreamHandler { private static final Logger LOG = Logger.getLogger(MessageDispatchHandler.class.getName()); public static final String NAME = "messageDispatch"; private final NettyChannelRegistry registry; private final ReentrantLock listenerLock; // guarded by listenerLock private volatile MessageArrivalListener listener; // guarded by listenerLock private final Queue<Runnable> events; public MessageDispatchHandler(NettyChannelRegistry registry) { this.registry = registry; listenerLock = new ReentrantLock(); events = new LinkedList<Runnable>(); } @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { dispatchImportantListenerEvent(new Runnable() { public void run() { listener.connectionDied(); } }); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { final MessageArrivalListener l = listener; if (l != null) { l.connectionDisposed(); } } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { if(e.getMessage() instanceof WelcomeMessage) { WelcomeMessage message = (WelcomeMessage) e.getMessage(); EndpointAddress logicalDestinationAddr = new EndpointAddress("jxta", message.getPeerID().getUniqueValue().toString(), null, null); registry.newConnection(ctx.getChannel(), message.getDestinationAddress(), logicalDestinationAddr); return; } final Message message = (Message) e.getMessage(); dispatchImportantListenerEvent(new Runnable() { public void run() { listener.messageArrived(message); } }); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { Throwable cause = e.getCause(); if(cause instanceof ConnectException) { LOG.log(Level.FINE, "Unable to connect to remote host"); } else if(cause instanceof ClosedChannelException) { LOG.log(Level.FINE, "Channel to {0} has been closed", new Object[] { ctx.getChannel().getRemoteAddress() }); } else if(cause instanceof IOException) { LOG.log(Level.FINE, "Channel to {0} has failed - {1}", new Object[] { ctx.getChannel().getRemoteAddress(), cause.getMessage() }); } else if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Unhandled exception in netty channel pipeline - closing connection", cause); } dispatchImportantListenerEvent(new Runnable() { public void run() { listener.connectionDied(); } }); Channels.close(ctx, ctx.getChannel().getCloseFuture()); } @Override public void channelInterestChanged(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { dispatchListenerEvent(new Runnable() { public void run() { listener.channelSaturated(ctx.getChannel().getInterestOps() == Channel.OP_READ_WRITE); } }); } public synchronized void setMessageArrivalListener(MessageArrivalListener listener) { listenerLock.lock(); try { this.listener = listener; Runnable eventDispatcher; while((eventDispatcher = events.poll()) != null) { eventDispatcher.run(); } } finally { listenerLock.unlock(); } } /** * Runs the provided runnable if the listener has been set, otherwise * queues the runnable until the listener has been set. Use this for * critical events that the listener must know about. */ private void dispatchImportantListenerEvent(Runnable r) { listenerLock.lock(); try { if(listener == null) { events.add(r); } else { r.run(); } } finally { listenerLock.unlock(); } } /** * Runs the provided runnable if the listener has been set. Use this * instead of {@link #dispatchImportantListenerEvent(Runnable)} if the * event is a transient hint rather than critical information. */ private void dispatchListenerEvent(Runnable r) { listenerLock.lock(); try { if(listener != null) { r.run(); } } finally { listenerLock.unlock(); } } }