/**
*
*/
package vnet.sms.gateway.nettysupport.window;
import static org.apache.commons.lang.Validate.notNull;
import java.io.Serializable;
import java.util.Map;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.UpstreamMessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vnet.sms.common.messages.GsmPdu;
import vnet.sms.common.wme.WindowedMessageEvent;
import vnet.sms.common.wme.acknowledge.SendMessageAcknowledgementEvent;
import vnet.sms.gateway.nettysupport.ChannelUtils;
import vnet.sms.gateway.nettysupport.window.incoming.IncomingWindowStore;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.MetricsRegistry;
/**
* @author obergner
*
*/
public class WindowingChannelHandler<ID extends Serializable> extends
SimpleChannelHandler {
public static final String NAME = "vnet.sms.gateway:incoming-outgoing-windowing-handler";
private final Logger log = LoggerFactory
.getLogger(getClass());
private final MetricsRegistry metricsRegistry;
private final IncomingWindowStore<ID> incomingWindowStore;
private Gauge<Integer> maximumWindowCapacity;
private Gauge<Long> maxWindowWaitTimeMillis;
private Gauge<Integer> currentlyUsedWindows;
public WindowingChannelHandler(
final IncomingWindowStore<ID> incomingWindowStore,
final MetricsRegistry metricsRegistry) {
notNull(incomingWindowStore,
"Argument 'incomingWindowStore' cannot be null");
notNull(metricsRegistry, "Argument 'metricsRegistry' must not be null");
this.incomingWindowStore = incomingWindowStore;
this.metricsRegistry = metricsRegistry;
}
// ------------------------------------------------------------------------
// Publish metrics
// ------------------------------------------------------------------------
/**
* @return the maximumWindowCapacity
*/
public final Gauge<Integer> getMaximumWindowCapacity() {
return this.maximumWindowCapacity;
}
/**
* @return the maxWindowWaitTimeMillis
*/
public final Gauge<Long> getMaxWindowWaitTimeMillis() {
return this.maxWindowWaitTimeMillis;
}
/**
* @return the currentlyUsedWindows
*/
public final Gauge<Integer> getCurrentlyUsedWindows() {
return this.currentlyUsedWindows;
}
// ------------------------------------------------------------------------
// Store incoming windowed messages in window store
// ------------------------------------------------------------------------
@Override
public final void messageReceived(final ChannelHandlerContext ctx,
final MessageEvent e) throws Exception {
if (!(e instanceof WindowedMessageEvent)) {
throw new IllegalStateException("Unsupported MessageEvent type: "
+ e);
}
windowedMessageReceived(ctx,
(WindowedMessageEvent<ID, ? extends GsmPdu>) e);
}
private void windowedMessageReceived(final ChannelHandlerContext ctx,
final WindowedMessageEvent<ID, ? extends GsmPdu> e)
throws IllegalArgumentException, InterruptedException {
this.log.trace("Processing {} ...", e);
if (this.incomingWindowStore.tryAcquireWindow(e)) {
this.log.trace("Acquired free window for {}", e);
ctx.sendUpstream(e);
} else {
this.log.warn(
"No free window for {} available after waiting for {} milliseconds",
e, this.incomingWindowStore.getWaitTimeMillis());
ctx.sendUpstream(new NoWindowForIncomingMessageAvailableEvent(
(UpstreamMessageEvent) e, this.incomingWindowStore
.getMaximumCapacity(), this.incomingWindowStore
.getWaitTimeMillis()));
}
}
// ------------------------------------------------------------------------
// Release previously stored windowed messages when receiving Acks/Nacks
// ------------------------------------------------------------------------
/**
* @see org.jboss.netty.channel.SimpleChannelHandler#writeRequested(org.jboss.netty.channel.ChannelHandlerContext,
* org.jboss.netty.channel.MessageEvent)
*/
@Override
public void writeRequested(final ChannelHandlerContext ctx,
final MessageEvent e) throws Exception {
this.log.debug("Processing {} ...", e);
if (e instanceof SendMessageAcknowledgementEvent) {
final SendMessageAcknowledgementEvent<ID, ? extends GsmPdu> acknowledgedEvent = (SendMessageAcknowledgementEvent<ID, ? extends GsmPdu>) e;
releaseAcknowledgedMessage(ctx, acknowledgedEvent);
ctx.sendDownstream(acknowledgedEvent);
} else {
super.writeRequested(ctx, e);
}
this.log.debug("Finished processing {}", e);
}
private void releaseAcknowledgedMessage(
final ChannelHandlerContext ctx,
final SendMessageAcknowledgementEvent<ID, ? extends GsmPdu> acknowledgedEvent) {
try {
final GsmPdu acknowledgedMessage = acknowledgedEvent.getMessage();
this.log.debug(
"Received acknowledgement {} for message {} - will release said message from incoming windowing store",
acknowledgedEvent.getAcknowledgement(), acknowledgedMessage);
final ID acknowledgedMessageRef = acknowledgedEvent
.getAcknowledgedMessageReference();
final GsmPdu releasedMessage = this.incomingWindowStore
.releaseWindow(acknowledgedMessageRef);
if (!acknowledgedMessage.equals(releasedMessage)) {
throw new IllegalArgumentException(
"The acknowledged message "
+ acknowledgedMessage
+ " is not the message "
+ releasedMessage
+ " that has been stored in this incoming windowing store under the same message reference ["
+ acknowledgedMessageRef + "]");
}
this.log.debug("Released message {} from incoming windowing store",
releasedMessage);
} catch (final Exception ex) {
this.log.error("Failed to release acknowledged message "
+ acknowledgedEvent.getMessage()
+ " from incoming windowing store: " + ex.getMessage(), ex);
ctx.sendUpstream(FailedToReleaseAcknowledgedMessageEvent.fail(
acknowledgedEvent, ex));
}
}
// ------------------------------------------------------------------------
// Lifecycle
// ------------------------------------------------------------------------
@Override
public void channelConnected(final ChannelHandlerContext ctx,
final ChannelStateEvent e) throws Exception {
this.maximumWindowCapacity = this.metricsRegistry.newGauge(
maximumCapacityMetricName(e.getChannel()),
new Gauge<Integer>() {
@Override
public Integer value() {
return WindowingChannelHandler.this.incomingWindowStore
.getMaximumCapacity();
}
});
this.maxWindowWaitTimeMillis = this.metricsRegistry.newGauge(
maxWindowWaitTimeMillisMetricName(e.getChannel()),
new Gauge<Long>() {
@Override
public Long value() {
return WindowingChannelHandler.this.incomingWindowStore
.getWaitTimeMillis();
}
});
this.currentlyUsedWindows = this.metricsRegistry.newGauge(
currentlyUsedWindowsMetricName(e.getChannel()),
new Gauge<Integer>() {
@Override
public Integer value() {
return WindowingChannelHandler.this.incomingWindowStore
.getCurrentMessageCount();
}
});
super.channelConnected(ctx, e);
}
private MetricName currentlyUsedWindowsMetricName(final Channel channel) {
return new MetricName(Channel.class, "currently-used-windows",
ChannelUtils.toString(channel));
}
private MetricName maxWindowWaitTimeMillisMetricName(final Channel channel) {
return new MetricName(Channel.class, "max-window-wait-time-millis",
ChannelUtils.toString(channel));
}
private MetricName maximumCapacityMetricName(final Channel channel) {
return new MetricName(Channel.class, "maximum-windows-capacity",
ChannelUtils.toString(channel));
}
@Override
public void channelDisconnected(final ChannelHandlerContext ctx,
final ChannelStateEvent e) throws Exception {
this.metricsRegistry.removeMetric(maximumCapacityMetricName(e
.getChannel()));
this.metricsRegistry.removeMetric(maxWindowWaitTimeMillisMetricName(e
.getChannel()));
this.metricsRegistry.removeMetric(currentlyUsedWindowsMetricName(e
.getChannel()));
final Map<ID, GsmPdu> pendingMessages = this.incomingWindowStore
.shutDown();
if (!pendingMessages.isEmpty()) {
this.log.warn(
"Channel {} has been disconnected while {} messages still await acknowledgement - these messages will be DISCARDED",
ctx.getChannel(), pendingMessages.size());
final PendingWindowedMessagesDiscardedEvent<ID> pendingMessagesDiscarded = new PendingWindowedMessagesDiscardedEvent<ID>(
ctx.getChannel(), pendingMessages);
ctx.sendUpstream(pendingMessagesDiscarded);
}
super.channelDisconnected(ctx, e);
}
}