de.jackwhite20.comix.Comix.java Source code

Java tutorial

Introduction

Here is the source code for de.jackwhite20.comix.Comix.java

Source

/*
 * Copyright (c) 2015 "JackWhite20"
 *
 * This file is part of Comix.
 *
 * Comix is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.jackwhite20.comix;

import com.google.gson.GsonBuilder;
import de.jackwhite20.comix.command.CommandManager;
import de.jackwhite20.comix.command.commands.*;
import de.jackwhite20.comix.config.ComixConfig;
import de.jackwhite20.comix.config.ConfigLoader;
import de.jackwhite20.comix.config.ip.IPRange;
import de.jackwhite20.comix.config.response.StatusResponse;
import de.jackwhite20.comix.handler.ComixChannelInitializer;
import de.jackwhite20.comix.logging.ComixLogger;
import de.jackwhite20.comix.network.ComixClient;
import de.jackwhite20.comix.strategy.BalancingStrategy;
import de.jackwhite20.comix.strategy.RoundRobinBalancingStrategy;
import de.jackwhite20.comix.tasks.CheckTargets;
import de.jackwhite20.comix.util.TargetData;
import de.jackwhite20.comix.whitelist.Whitelist;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.ResourceLeakDetector;
import jline.console.ConsoleReader;
import jline.console.completer.StringsCompleter;
import org.fusesource.jansi.AnsiConsole;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

/**
 * Created by JackWhite20 on 13.07.2015.
 */
public class Comix implements Runnable {

    private static Comix instance;

    private static Logger logger;

    private static ConsoleReader consoleReader;

    private boolean running;

    private String balancerHost;

    private int balancerPort;

    private List<TargetData> targets = new ArrayList<>();

    private BalancingStrategy balancingStrategy;

    private ComixConfig comixConfig;

    private StatusResponse statusResponse;

    private String statusResponseString;

    private NioEventLoopGroup bossGroup;

    private NioEventLoopGroup workerGroup;

    private List<String> ipBlacklist = new ArrayList<>();

    private List<IPRange> ipRangeBlacklist = new ArrayList<>();

    private Whitelist whitelist;

    private List<ComixClient> clients = new ArrayList<>();

    private CommandManager commandManager = new CommandManager();

    public Comix() {
        instance = this;
        running = true;

        try {
            consoleReader = new ConsoleReader();
            consoleReader.setExpandEvents(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        System.setProperty("java.net.preferIPv4Stack", "true");

        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);

        AnsiConsole.systemInstall();

        LogManager.getLogManager().reset();

        logger = new ComixLogger(consoleReader);

        logger.log(Level.INFO, "Comix", "------ Comix v.0.1 ------");

        loadConfig();

        logger.log(Level.INFO, "Load-Balancer", (targets.size() > 0) ? "Targets:" : "No Target Servers found!");
        targets.forEach(t -> logger.log(Level.INFO, "Load-Balancer",
                t.getName() + " - " + t.getHost() + ":" + t.getPort()));

        logger.log(Level.INFO, "Commands", "Registering commands...");

        registerCommands();

        logger.log(Level.INFO, "Comix", "Starting Comix on " + balancerHost + ":" + balancerPort + "...");

        balancingStrategy = new RoundRobinBalancingStrategy(targets);

        new Timer("CheckTargets").scheduleAtFixedRate(new CheckTargets(balancingStrategy), 0,
                TimeUnit.SECONDS.toMillis(comixConfig.getCheckTime()));

        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup(comixConfig.getThreads());

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_BACKLOG, comixConfig.getBacklog())
                    .option(ChannelOption.SO_REUSEADDR, true).childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.AUTO_READ, false).childOption(ChannelOption.SO_TIMEOUT, 4000)
                    .childHandler(new ComixChannelInitializer());

            ChannelFuture f = bootstrap.bind(comixConfig.getPort()).sync();

            reload();

            logger.log(Level.INFO, "Comix", "Comix is started!");

            f.channel().closeFuture().sync();

            running = false;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            shutdown();
        }
    }

    private void registerCommands() {
        commandManager.addCommand(new HelpCommand("help", new String[] { "h", "?" }, "List of commands"));
        commandManager.addCommand(new ReloadCommand("reload", new String[] { "r" },
                "Reloads 'ip-blacklist.comix', 'status.comix' and 'whitelist.comix'"));
        commandManager.addCommand(
                new MaintenanceCommand("maintenance", new String[] { "m" }, "Switches between Maintenance"));
        commandManager
                .addCommand(new KickallCommand("kickall", new String[] { "ka" }, "Kicks all players from Comix"));
        commandManager.addCommand(new ClearCommand("clear", new String[] { "c" }, "Clears the screen"));
        commandManager.addCommand(new StopCommand("stop", new String[] { "end" }, "Stops Comix"));
        commandManager.addCommand(new StatsCommand("stats", new String[] {},
                "Stats about total traffic in and out from currently conencted clients"));

        List<String> cmds = new ArrayList<>();
        commandManager.getCommands().forEach(c -> cmds.add(c.getName()));
        consoleReader.addCompleter(new StringsCompleter(cmds));
    }

    public void kickAll() {
        clients.forEach(c -> c.getUpstreamHandler().getUpstreamChannel().close());
    }

    public void reload() {
        loadIpBlacklist();
        loadWhitelist();
        loadStatusResponse();
    }

    public boolean maintainMode() {
        if (!comixConfig.isMaintenance()) {
            comixConfig.setMaintenance(true);
            saveConfig();
            loadStatusResponse();

            return true;
        } else {
            comixConfig.setMaintenance(false);
            saveConfig();
            loadStatusResponse();

            return false;
        }
    }

    public void saveConfig() {
        String status = new GsonBuilder().setPrettyPrinting().create().toJson(comixConfig, ComixConfig.class);

        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(new File("config.comix")));
            writer.write(status);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        start();
    }

    private void loadConfig() {
        try {
            this.comixConfig = ConfigLoader.loadConfig("config.comix", ComixConfig.class);

            this.balancerHost = comixConfig.getHost();
            this.balancerPort = comixConfig.getPort();
            this.targets = comixConfig.getTargets();

            logger.log(Level.INFO, "Config", "Config loaded...");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Unable to load Comix Config file!");
            System.exit(1);
        }
    }

    private void loadWhitelist() {
        try {
            new File("whitelist.comix").createNewFile();

            whitelist = ConfigLoader.loadConfig("whitelist.comix", Whitelist.class);

            if (!whitelist.isEnabled())
                logger.log(Level.INFO, "Whitelist", "Whitelist loaded...");
            else
                logger.info("Whitelisted: " + String.join(", ", whitelist.getNames()));
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Error while loading 'whitelist.comix'!");
        }
    }

    public boolean isIpRangeBanned(InetSocketAddress ip) {
        for (IPRange range : ipRangeBlacklist) {
            if (!range.isAllowed(ip))
                return true;
        }

        return false;
    }

    public boolean isIpBanned(String ip) {
        return ipBlacklist.contains(ip);
    }

    public boolean isWhitelisted(String name) {
        return whitelist.getNames().contains(name);
    }

    public boolean isWhitelistEnabled() {
        return whitelist.isEnabled();
    }

    public String getWhitelistKickMessage() {
        return whitelist.getMessage();
    }

    public void shutdown() {
        running = false;

        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();

        //TODO: Shutdown more "nicely"
        System.exit(0);
    }

    private void loadIpBlacklist() {
        try {
            new File("ip-blacklist.comix").createNewFile();

            ConfigLoader.loadIpBlacklist(ipBlacklist, ipRangeBlacklist);

            logger.log(Level.INFO, "IP-Blacklist", "File loaded...");

            if (ipBlacklist.size() > 0)
                logger.log(Level.INFO, "IP-Blacklist", ipBlacklist.size() + " IPs loaded!");

            if (ipRangeBlacklist.size() > 0)
                logger.log(Level.INFO, "IP-Blacklist", ipRangeBlacklist.size() + " IP-Ranges loaded!");
        } catch (Exception e) {
            logger.log(Level.WARNING, "Error while loading ip-blacklist.comix: " + e.getMessage());
        }
    }

    public static String encodeToString(BufferedImage image, String type) {
        String imageString;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        try {
            ImageIO.write(image, type, bos);
            byte[] imageBytes = bos.toByteArray();

            BASE64Encoder encoder = new BASE64Encoder();
            imageString = encoder.encode(imageBytes);

            bos.close();
        } catch (IOException e) {
            logger.log(Level.WARNING, "Error while loading favicon: " + e.getMessage());
            return "";
        }

        logger.log(Level.INFO, "Status", "Favicon loaded...");

        return imageString;
    }

    public void loadStatusResponse() {
        try {
            String faviconString = encodeToString(ImageIO.read(new File("favicon.png")), "png");

            statusResponse = ConfigLoader.loadConfig("status.comix", StatusResponse.class);

            if (!comixConfig.isMaintenance())
                statusResponseString = "{\"version\":{\"name\":\"" + statusResponse.getVersion().getName()
                        + "\",\"protocol\":" + statusResponse.getVersion().getProtocol() + "},\"players\":{\"max\":"
                        + statusResponse.getPlayers().getMax() + ",\"online\":"
                        + statusResponse.getPlayers().getOnline() + ", \"sample\":[{\"name\":\""
                        + statusResponse.getPlayers().getSample()
                        + "\",\"id\":\"00000000-0000-0000-0000-000000000000\"}]},\"description\":\""
                        + statusResponse.getDescription() + "\",\"favicon\":\"data:image/png;base64,"
                        + faviconString + "\",\"modinfo\":{\"type\":\"FML\",\"modList\":[]}}";
            else {
                statusResponseString = "{\"version\":{\"name\":\"" + comixConfig.getMaintenancePingMessage()
                        + "\",\"protocol\":0},\"players\":{\"max\":" + statusResponse.getPlayers().getMax()
                        + ",\"online\":" + statusResponse.getPlayers().getOnline() + "},\"description\":\""
                        + comixConfig.getMaintenanceDescription() + "\",\"favicon\":\"data:image/png;base64,"
                        + faviconString + "\",\"modinfo\":{\"type\":\"FML\",\"modList\":[]}}";
            }

            logger.log(Level.INFO, "Status", "Status Response loaded...");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Error while loading status.comix");
            e.printStackTrace();
        }
    }

    public synchronized void addClient(ComixClient comixClient) {
        clients.add(comixClient);
    }

    public synchronized void removeClient(ComixClient comixClient) {
        clients.remove(comixClient);
    }

    public int getClientsOnline() {
        return clients.size();
    }

    public List<ComixClient> getClients() {
        return clients;
    }

    public boolean isRunning() {
        return running;
    }

    public ComixConfig getComixConfig() {
        return comixConfig;
    }

    public String getStatusResponseString() {
        return statusResponseString;
    }

    public BalancingStrategy getBalancingStrategy() {
        return balancingStrategy;
    }

    public static ConsoleReader getConsoleReader() {
        return consoleReader;
    }

    public CommandManager getCommandManager() {
        return commandManager;
    }

    public static Logger getLogger() {
        return logger;
    }

    public static Comix getInstance() {
        return instance;
    }

}