// Path: src/main/java/org/jlange/proxy/plugin/PluginProvider.java
// public class PluginProvider {
// 
//     private final static Logger         log      = LoggerFactory.getLogger(PluginProvider.class);
//     private final static PluginProvider instance = new PluginProvider();
// 
//     public static PluginProvider getInstance() {
//         return instance;
//     }
// 
//     private final List<ResponsePlugin>           responsePlugins;
//     private final List<PredefinedResponsePlugin> predefinedResponsePlugins;
// 
//     private PluginProvider() {
//         responsePlugins = new ArrayList<ResponsePlugin>();
// 
//         for (String plugin : Config.PLUGINS_RESPONSE) {
//             try {
//                 Class<?> className = Class.forName(plugin);
//                 responsePlugins.add((ResponsePlugin) className.newInstance());
//                 log.info("Plugin loaded: {}", plugin);
//             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
//                 log.error("Could not load plugin: {}", e.getMessage());
//             }
//         }
// 
//         predefinedResponsePlugins = new ArrayList<PredefinedResponsePlugin>();
// 
//         for (String plugin : Config.PLUGINS_PREDEFINED) {
//             try {
//                 Class<?> className = Class.forName(plugin);
//                 predefinedResponsePlugins.add((PredefinedResponsePlugin) className.newInstance());
//                 log.info("Plugin loaded: {}", plugin);
//             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
//                 log.error("Could not load plugin: {}", e.getMessage());
//             }
//         }
//     }
// 
//     public List<ResponsePlugin> getResponsePlugins() {
//         return responsePlugins;
//     }
// 
//     public List<PredefinedResponsePlugin> getPredefinedResponsePlugins() {
//         return predefinedResponsePlugins;
//     }
// }
// 
// Path: src/main/java/org/jlange/proxy/plugin/PredefinedResponsePlugin.java
// public interface PredefinedResponsePlugin {
// 
//     /**
//      * If the plugin is able to define a {@link HttpResponse} for the given {@link HttpRequest} this method will return it. Otherwise
//      * {@code null} is returned.
//      * 
//      * @param request {@link HttpRequest}
//      * @return a {@link HttpResponse} or {@code null}
//      */
//     public HttpResponse getPredefinedResponse(final HttpRequest request);
// }
// 
// Path: src/main/java/org/jlange/proxy/plugin/ResponsePlugin.java
// public interface ResponsePlugin {
// 
//     /**
//      * Decides if the plugin is applicable for the given request
//      * 
//      * @param request {@link HttpRequest}
//      * @return is the plugin applicable
//      */
//     public Boolean isApplicable(final HttpRequest request);
// 
//     /**
//      * Decides if the plugin is applicable for the given response
//      * 
//      * @param request {@link HttpRequest}
//      * @return is the plugin applicable
//      */
//     public Boolean isApplicable(final HttpResponse response);
// 
//     /**
//      * Run the main code of the plugin. The response will be updated in here. For read and changing responses content the code must be
//      * marked as <code>synchronized</code>, because these plugins are intended to run in parallel.
//      * 
//      * @param request {@link HttpRequest}
//      * @param response matching {@link HttpResponse}
//      */
//     public void run(final HttpRequest request, final HttpResponse response);
// 
//     /**
//      * Run the main code of the plugin for a given chunk.
//      * 
//      * @param request {@link HttpRequest}
//      * @param chunk {@link HttpChunk}
//      */
//     public void run(HttpRequest request, HttpChunk chunk);
// }


import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jlange.proxy.plugin.PluginProvider;
import org.jlange.proxy.plugin.PredefinedResponsePlugin;
import org.jlange.proxy.plugin.ResponsePlugin;
import org.slf4j.Logger;

            // select applicable plugins
            Iterator<ResponsePlugin> iter = responsePlugins.iterator();
            while(iter.hasNext())
                if (!iter.next().isApplicable(response))
                    iter.remove();
            
            // apply response plugins
            for (ResponsePlugin plugin : responsePlugins) {
                long start = System.currentTimeMillis();
                plugin.run(request, response);
                logStats(plugin, start);
            }
        }
        else if (e.getMessage() instanceof HttpChunk) {
            HttpChunk chunk = (HttpChunk) e.getMessage();
            
            // apply response plugins
            for (ResponsePlugin plugin : responsePlugins) {
                long start = System.currentTimeMillis();
                plugin.run(request, chunk);
                logStats(plugin, start);
            }
        }

        super.messageReceived(ctx, e);
    }

    @Override
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
// Path: src/main/java/org/jlange/proxy/plugin/PluginProvider.java
// public class PluginProvider {
// 
//     private final static Logger         log      = LoggerFactory.getLogger(PluginProvider.class);
//     private final static PluginProvider instance = new PluginProvider();
// 
//     public static PluginProvider getInstance() {
//         return instance;
//     }
// 
//     private final List<ResponsePlugin>           responsePlugins;
//     private final List<PredefinedResponsePlugin> predefinedResponsePlugins;
// 
//     private PluginProvider() {
//         responsePlugins = new ArrayList<ResponsePlugin>();
// 
//         for (String plugin : Config.PLUGINS_RESPONSE) {
//             try {
//                 Class<?> className = Class.forName(plugin);
//                 responsePlugins.add((ResponsePlugin) className.newInstance());
//                 log.info("Plugin loaded: {}", plugin);
//             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
//                 log.error("Could not load plugin: {}", e.getMessage());
//             }
//         }
// 
//         predefinedResponsePlugins = new ArrayList<PredefinedResponsePlugin>();
// 
//         for (String plugin : Config.PLUGINS_PREDEFINED) {
//             try {
//                 Class<?> className = Class.forName(plugin);
//                 predefinedResponsePlugins.add((PredefinedResponsePlugin) className.newInstance());
//                 log.info("Plugin loaded: {}", plugin);
//             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
//                 log.error("Could not load plugin: {}", e.getMessage());
//             }
//         }
//     }
// 
//     public List<ResponsePlugin> getResponsePlugins() {
//         return responsePlugins;
//     }
// 
//     public List<PredefinedResponsePlugin> getPredefinedResponsePlugins() {
//         return predefinedResponsePlugins;
//     }
// }
// 
// Path: src/main/java/org/jlange/proxy/plugin/PredefinedResponsePlugin.java
// public interface PredefinedResponsePlugin {
// 
//     /**
//      * If the plugin is able to define a {@link HttpResponse} for the given {@link HttpRequest} this method will return it. Otherwise
//      * {@code null} is returned.
//      * 
//      * @param request {@link HttpRequest}
//      * @return a {@link HttpResponse} or {@code null}
//      */
//     public HttpResponse getPredefinedResponse(final HttpRequest request);
// }
// 
// Path: src/main/java/org/jlange/proxy/plugin/ResponsePlugin.java
// public interface ResponsePlugin {
// 
//     /**
//      * Decides if the plugin is applicable for the given request
//      * 
//      * @param request {@link HttpRequest}
//      * @return is the plugin applicable
//      */
//     public Boolean isApplicable(final HttpRequest request);
// 
//     /**
//      * Decides if the plugin is applicable for the given response
//      * 
//      * @param request {@link HttpRequest}
//      * @return is the plugin applicable
//      */
//     public Boolean isApplicable(final HttpResponse response);
// 
//     /**
//      * Run the main code of the plugin. The response will be updated in here. For read and changing responses content the code must be
//      * marked as <code>synchronized</code>, because these plugins are intended to run in parallel.
//      * 
//      * @param request {@link HttpRequest}
//      * @param response matching {@link HttpResponse}
//      */
//     public void run(final HttpRequest request, final HttpResponse response);
// 
//     /**
//      * Run the main code of the plugin for a given chunk.
//      * 
//      * @param request {@link HttpRequest}
//      * @param chunk {@link HttpChunk}
//      */
//     public void run(HttpRequest request, HttpChunk chunk);
// }


// Path: src/main/java/org/jlange/proxy/outbound/PluginHandler.java
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jlange.proxy.plugin.PluginProvider;
import org.jlange.proxy.plugin.PredefinedResponsePlugin;
import org.jlange.proxy.plugin.ResponsePlugin;
import org.slf4j.Logger;


            // select applicable plugins
            Iterator<ResponsePlugin> iter = responsePlugins.iterator();
            while(iter.hasNext())
                if (!iter.next().isApplicable(response))
                    iter.remove();
            
            // apply response plugins
            for (ResponsePlugin plugin : responsePlugins) {
                long start = System.currentTimeMillis();
                plugin.run(request, response);
                logStats(plugin, start);
            }
        }
        else if (e.getMessage() instanceof HttpChunk) {
            HttpChunk chunk = (HttpChunk) e.getMessage();
            
            // apply response plugins
            for (ResponsePlugin plugin : responsePlugins) {
                long start = System.currentTimeMillis();
                plugin.run(request, chunk);
                logStats(plugin, start);
            }
        }

        super.messageReceived(ctx, e);
    }

    @Override
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        HttpRequest request = (HttpRequest) e.getMessage();