/** * */ package vnet.routing.netty.server.support.ping; import static org.apache.commons.lang.Validate.notNull; import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.inject.Qualifier; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.ChannelGroupFuture; import org.jboss.netty.channel.group.ChannelGroupFutureListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedOperationParameter; import org.springframework.jmx.export.annotation.ManagedOperationParameters; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.stereotype.Service; /** * @author obergner * */ @ManagedResource(objectName = PingService.OBJECT_NAME, description = "A service for periodically sending a configurable ping message via a set of channels") @PingService.Default @Service public class PingService<P extends Serializable> { @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Default { } public static final String OBJECT_NAME = "vnet.routing.netty.server.support:service=PingService,version=1.0.0-SNAPSHOT"; private final Logger log = LoggerFactory.getLogger(getClass()); private final ChannelGroup pingChannels; private final PingFactory<P> pingFactory; private final ScheduledExecutorService periodicTaskScheduler; private int pingIntervalInSeconds; private final PingSender pingSender = new PingSender(); private ScheduledFuture<?> pingSenderHandle; public PingService(final ChannelGroup pingChannels, final PingFactory<P> pingFactory, final ScheduledExecutorService periodicTaskScheduler, final int pingIntervalInSeconds) { notNull(pingChannels, "Argument 'pingChannels' must not be null"); notNull(pingFactory, "Argument 'pingFactory' must not be null"); notNull(periodicTaskScheduler, "Argument 'periodicTaskScheduler' must not be null"); this.pingChannels = pingChannels; this.pingFactory = pingFactory; this.periodicTaskScheduler = periodicTaskScheduler; this.pingIntervalInSeconds = pingIntervalInSeconds; } @ManagedAttribute(description = "The interval between two pings in seconds") public int getPingIntervalInSeconds() { return this.pingIntervalInSeconds; } @ManagedOperation(description = "Start this service") public synchronized void start() { if (this.pingSenderHandle != null) { // Already started return; } this.log.info("Starting PingService {} ...", this); this.pingSenderHandle = this.periodicTaskScheduler.scheduleAtFixedRate( this.pingSender, 0L, this.pingIntervalInSeconds, TimeUnit.SECONDS); this.log.info("PingService {} started", this); } @ManagedOperation(description = "Pause this service") public synchronized void pause() throws IllegalStateException { if (this.pingSenderHandle == null) { // Already paused/stopped return; } this.log.info("Pausing PingService {} ...", this); final boolean successfullyCancelled = this.pingSenderHandle .cancel(false); this.pingSenderHandle = null; if (!successfullyCancelled) { throw new IllegalStateException("Failed to cancel ping sender"); } this.log.info("PingService {} paused", this); } @ManagedOperation(description = "Modify this service's ping interval") @ManagedOperationParameters(@ManagedOperationParameter(name = "newPingIntervalInSeconds", description = "The new interval between two pings in seconds")) public synchronized void reschedule(final int newPingIntervalInSeconds) throws IllegalStateException { this.log.info("Rescheduling PingSender to new interval [{}] ...", newPingIntervalInSeconds); pause(); this.pingIntervalInSeconds = newPingIntervalInSeconds; start(); this.log.info("PingSender rescheduled to new interval [{}]", newPingIntervalInSeconds); } @ManagedOperation(description = "Stop this service") public synchronized void stop() { if (this.periodicTaskScheduler.isShutdown()) { // Already stopped return; } try { this.log.info("Stopping PingService {} ...", this); pause(); this.periodicTaskScheduler.shutdown(); this.periodicTaskScheduler.awaitTermination(2, TimeUnit.SECONDS); } catch (final InterruptedException e) { this.log.warn( "Caught InterruptedException while in the process of cancelling periodic ping sending: " + e.getMessage(), e); Thread.currentThread().interrupt(); } finally { this.periodicTaskScheduler.shutdownNow(); this.log.info("PingService {} stopped", this); } } @Override public String toString() { return "PingService@" + hashCode() + "[pingChannels = " + this.pingChannels + "|pingFactory = " + this.pingFactory + "|pingIntervalInSeconds = " + this.pingIntervalInSeconds + "|pingSender = " + this.pingSender + "]"; } private class PingSender implements Runnable { @Override public void run() { final P pingMessage = PingService.this.pingFactory.newPing(); final ChannelGroupFuture allPingsHaveBeenSent = PingService.this.pingChannels .write(pingMessage); allPingsHaveBeenSent.addListener(new ChannelGroupFutureListener() { @Override public void operationComplete( final ChannelGroupFuture allPingsSent) throws Exception { for (final ChannelFuture pingSent : allPingsSent) { if (!pingSent.isSuccess()) { PingService.this.log.warn( "Failed to send ping via channel " + pingSent.getChannel() + ": " + pingSent.getCause().getMessage(), pingSent.getCause()); } else { PingService.this.log.trace( "Sent ping via Channel {}", pingSent.getChannel()); } } } }); } } }