package com.tacitknowledge.slowlight.proxyserver.handler;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.MapConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.tacitknowledge.slowlight.proxyserver.config.BehaviorFunctionConfig;
import com.tacitknowledge.slowlight.proxyserver.config.HandlerConfig;
import com.tacitknowledge.slowlight.proxyserver.handler.behavior.BehaviorFunction;
/**
* Implementation of slow-light abstract handler.
* Use this abstract handler as extension point for all existing and future handlers.
*
* @author Alexandr Donciu (adonciu@tacitknowledge.com)
*/
@ChannelHandler.Sharable
public abstract class AbstractChannelHandler extends ChannelDuplexHandler
{
public static final String TIME_FRAME = "timeFrame";
public static final int ZERO_TIME_FRAME = 0;
private static final Logger LOG = LoggerFactory.getLogger(AbstractChannelHandler.class);
protected AbstractConfiguration handlerParams = new MapConfiguration(new HashMap<String, Object>());
protected HandlerConfig handlerConfig;
protected Map<String, BehaviorFunction> behaviorFunctions = new HashMap<String, BehaviorFunction>();
public AbstractChannelHandler(final HandlerConfig handlerConfig)
{
this.handlerConfig = handlerConfig;
initBehaviorFunctions();
initTimeFrameTask();
registerHandlerConfig();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
LOG.error("Error occurred while executing channel handler", cause);
closeOnFlush(ctx.channel());
}
/**
* Flush all pending messages and then close the channel.
*
* @param channel the channel to be actioned
*/
public void closeOnFlush(Channel channel)
{
if (channel != null && channel.isActive())
{
channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
/**
* Gets the current handler time frame configuration.
*
* @return time frame configuration (in seconds)
*/
protected long getTimeFrame()
{
final String timeFrameProp = handlerConfig.getParam(TIME_FRAME, false);
return timeFrameProp == null ? ZERO_TIME_FRAME : Long.parseLong(timeFrameProp);
}
/**
* This callback method gets invoked per each time frame.
* Use it to do any handler time dependent changes (e.g. update handler parameters, expose metrics, etc.)
*/
protected void timerCallback()
{
// EMPTY
}
/**
* Override this method in a concrete handler to lookup, transform and populate all required parameters.
*/
protected void populateHandlerParams()
{
// EMPTY
}
/**
* Cycles through all defined behaviour function {@link com.tacitknowledge.slowlight.proxyserver.handler.behavior.IntervalBehaviorFunction}
* and evaluates handler parameters based on function result.
*/
protected void evaluateBehaviorFunctions()
{
for (final BehaviorFunctionConfig behaviorFunctionConfig : handlerConfig.getBehaviorFunctions())
{
final BehaviorFunction behaviorFunction = behaviorFunctions.get(behaviorFunctionConfig.getId());
if (behaviorFunction.shouldEvaluate(behaviorFunctionConfig)) {
handlerParams.setProperty(
behaviorFunctionConfig.getParamName(), behaviorFunction
.evaluate(behaviorFunctionConfig.getParams()));
}
}
}
private void initTimeFrameTask()
{
new TimeFrameTask().start();
}
private void registerHandlerConfig()
{
populateHandlerParams();
if (!handlerParams.isEmpty())
{
HandlerConfigManager.registerConfigMBean(handlerConfig, handlerParams);
}
}
private void initBehaviorFunctions()
{
for (final BehaviorFunctionConfig behaviorFunctionConfig : handlerConfig.getBehaviorFunctions())
{
if (!handlerConfig.getParams().containsKey(behaviorFunctionConfig.getParamName()))
{
throw new IllegalArgumentException("Cannot map behavior function to specified handler param ["
+ behaviorFunctionConfig.getParamName() + "] because it doesn't exists ");
}
behaviorFunctions.put(behaviorFunctionConfig.getId(), createBehaviorFunction(behaviorFunctionConfig));
}
}
private BehaviorFunction createBehaviorFunction(final BehaviorFunctionConfig config)
{
try
{
final Class<?> behaviorFunctionClass = Class.forName(config.getType());
return (BehaviorFunction) behaviorFunctionClass.getConstructor()
.newInstance();
}
catch (Exception e)
{
throw new IllegalArgumentException("Cannot create behavior function by specified name [" + config.getType() + "]", e);
}
}
public HandlerConfig getHandlerConfig()
{
return handlerConfig;
}
/**
* This class drives the handler time frame functionality.
*/
protected class TimeFrameTask implements TimerTask
{
protected Timer timer;
public void start()
{
if (getTimeFrame() > ZERO_TIME_FRAME)
{
if (timer == null)
{
timer = new HashedWheelTimer(1, TimeUnit.SECONDS);
}
schedule();
}
}
@Override
public void run(Timeout timeout) throws Exception
{
evaluateBehaviorFunctions();
timerCallback();
schedule();
}
protected void schedule()
{
timer.newTimeout(this, getTimeFrame(), TimeUnit.SECONDS);
}
}
}