/** * */ package vnet.sms.gateway.nettysupport.ping.outgoing; import static org.apache.commons.lang.Validate.notNull; import java.io.Serializable; import java.util.concurrent.TimeUnit; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; import org.jboss.netty.util.TimerTask; import vnet.sms.common.messages.PingRequest; import vnet.sms.common.wme.receive.ReceivedPingRequestAcknowledgementEvent; import vnet.sms.common.wme.send.SendPingRequestEvent; import vnet.sms.gateway.nettysupport.UpstreamWindowedChannelHandler; import vnet.sms.gateway.nettysupport.login.incoming.ChannelSuccessfullyAuthenticatedEvent; import vnet.sms.gateway.nettysupport.window.spi.MessageReferenceGenerator; /** * @author obergner * */ public class OutgoingPingChannelHandler<ID extends Serializable> extends UpstreamWindowedChannelHandler<ID> { public static final String NAME = "vnet.sms.gateway:outgoing-ping-handler"; private final int pingIntervalSeconds; private final long pingResponseTimeoutMillis; private final MessageReferenceGenerator<ID> windowIdGenerator; private final Timer pingIntervalTimer; private final Timer pingResponseTimeoutTimer; private volatile PingSender pingSender; public OutgoingPingChannelHandler(final int pingIntervalSeconds, final long pingResponseTimeoutMillis, final MessageReferenceGenerator<ID> windowIdGenerator, final Timer pingIntervalTimer, final Timer pingResponseTimeoutTimer) { notNull(windowIdGenerator, "Argument 'windowIdGenerator' must not be null"); notNull(pingIntervalTimer, "Argument 'pingIntervalTimer' must not be null"); notNull(pingResponseTimeoutTimer, "Argument 'pingResponseTimeoutTimer' must not be null"); this.pingIntervalSeconds = pingIntervalSeconds; this.pingResponseTimeoutMillis = pingResponseTimeoutMillis; this.windowIdGenerator = windowIdGenerator; this.pingIntervalTimer = pingIntervalTimer; this.pingResponseTimeoutTimer = pingResponseTimeoutTimer; } public int getPingIntervalSeconds() { return this.pingIntervalSeconds; } public long getPingResponseTimeoutMillis() { return this.pingResponseTimeoutMillis; } /** * @see vnet.sms.gateway.nettysupport.UpstreamWindowedChannelHandler#channelSuccessfullyAuthenticated(org.jboss.netty.channel.ChannelHandlerContext, * vnet.sms.gateway.nettysupport.login.incoming.ChannelSuccessfullyAuthenticatedEvent) */ @Override protected void channelSuccessfullyAuthenticated( final ChannelHandlerContext ctx, final ChannelSuccessfullyAuthenticatedEvent e) throws Exception { getLog().info( "Channel {} has been successfully authenticated - will start to ping in [{}] seconds", ctx.getChannel(), this.pingIntervalSeconds); this.pingSender = startPingSenderTask(ctx); super.channelSuccessfullyAuthenticated(ctx, e); } private PingSender startPingSenderTask(final ChannelHandlerContext ctx) { final PingSender pingSender = new PingSender(ctx); this.pingIntervalTimer.newTimeout(pingSender, this.pingIntervalSeconds, TimeUnit.SECONDS); return pingSender; } @Override public void pingResponseReceived(final ChannelHandlerContext ctx, final ReceivedPingRequestAcknowledgementEvent<ID> e) throws Exception { if (this.pingSender == null) { throw new IllegalStateException( "Cannot cancel ping response timout since no PingSender has been started - have you started a PingSender in channelConnected(...)?"); } getLog().debug( "Received response {} to previously sent ping request within timeout of [{}] milliseconds - will send out next ping after [{}] seconds", new Object[] { e, this.pingResponseTimeoutMillis, this.pingIntervalSeconds }); this.pingSender.cancelPingResponseTimeout(); restartPingSenderTask(); super.pingResponseReceived(ctx, e); } private void restartPingSenderTask() { if (this.pingSender == null) { throw new IllegalStateException( "Cannot restart a null PingSender - have you started a PingSender in channelConnected(...)?"); } this.pingIntervalTimer.newTimeout(this.pingSender, this.pingIntervalSeconds, TimeUnit.SECONDS); } private final class PingSender implements TimerTask { private final ChannelHandlerContext ctx; private volatile Timeout pingResponseTimeout; PingSender(final ChannelHandlerContext ctx) { this.ctx = ctx; } @Override public void run(final Timeout timeout) throws Exception { if (timeout.isCancelled() || !this.ctx.getChannel().isOpen()) { return; } sendPingRequestDownstream(); // Inform the wider community sendStartedToPingEventUpstream(); this.pingResponseTimeout = OutgoingPingChannelHandler.this.pingResponseTimeoutTimer .newTimeout( new PingResponseTimeout(this.ctx), OutgoingPingChannelHandler.this.pingResponseTimeoutMillis, TimeUnit.MILLISECONDS); } private void sendPingRequestDownstream() { final SendPingRequestEvent<ID> sendPingRequestEvent = createSendPingRequestEvent(); getLog().debug( "Sending {} on channel {} - will wait [{}] milliseconds for a response before issuing a request to close this channel", new Object[] { sendPingRequestEvent, this.ctx.getChannel(), OutgoingPingChannelHandler.this.pingResponseTimeoutMillis }); this.ctx.sendDownstream(sendPingRequestEvent); } private SendPingRequestEvent<ID> createSendPingRequestEvent() { final PingRequest pingRequest = new PingRequest(); return new SendPingRequestEvent<ID>( OutgoingPingChannelHandler.this.windowIdGenerator .nextMessageReference(), this.ctx.getChannel(), pingRequest); } private void sendStartedToPingEventUpstream() { final StartedToPingEvent startedToPing = new StartedToPingEvent( this.ctx.getChannel(), getPingIntervalSeconds(), getPingResponseTimeoutMillis()); this.ctx.sendUpstream(startedToPing); getLog().debug( "Sent {} upstream to inform upstream channel handlers that pinging commenced on channel {}", startedToPing, this.ctx.getChannel()); } boolean cancelPingResponseTimeout() { if ((this.pingResponseTimeout == null) || this.pingResponseTimeout.isCancelled() || this.pingResponseTimeout.isExpired()) { getLog().warn( "Attempt to cancel a ping response timeout [{}] that is either null or cancelled or already expired", this.pingResponseTimeout); return false; } this.pingResponseTimeout.cancel(); getLog().debug("Cancelled ping response timeout {}", this.pingResponseTimeout); this.pingResponseTimeout = null; return true; } } private final class PingResponseTimeout implements TimerTask { private final ChannelHandlerContext ctx; PingResponseTimeout(final ChannelHandlerContext ctx) { this.ctx = ctx; } @Override public void run(final Timeout timeout) throws Exception { if (timeout.isCancelled() || !this.ctx.getChannel().isOpen()) { return; } getLog().warn( "Did not receive response to ping request after timeout of [{}] milliseconds - will issue a request to close this channel", OutgoingPingChannelHandler.this.pingResponseTimeoutMillis); this.ctx.sendUpstream(new PingResponseTimeoutExpiredEvent(this.ctx .getChannel(), OutgoingPingChannelHandler.this.pingIntervalSeconds, OutgoingPingChannelHandler.this.pingResponseTimeoutMillis)); } } }