com.vexsoftware.votifier.NuVotifierBukkit.java Source code

Java tutorial

Introduction

Here is the source code for com.vexsoftware.votifier.NuVotifierBukkit.java

Source

/*
 * Copyright (C) 2012 Vex Software LLC
 * This file is part of Votifier.
 *
 * Votifier 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.
 *
 * Votifier 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 Votifier.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.vexsoftware.votifier;

import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
import com.vexsoftware.votifier.forwarding.ForwardedVoteListener;
import com.vexsoftware.votifier.forwarding.ForwardingVoteSink;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VotifierEvent;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

/**
 * The main Votifier plugin class.
 *
 * @author Blake Beaupain
 * @author Kramer Campbell
 */
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {

    /**
     * The Votifier instance.
     */
    private static NuVotifierBukkit instance;

    /**
     * The current Votifier version.
     */
    private String version;

    /**
     * The server channel.
     */
    private Channel serverChannel;

    /**
     * The event group handling the channel.
     */
    private NioEventLoopGroup serverGroup;

    /**
     * The RSA key pair.
     */
    private KeyPair keyPair;

    /**
     * Debug mode flag
     */
    private boolean debug;

    /**
     * Keys used for websites.
     */
    private Map<String, Key> tokens = new HashMap<>();

    private ForwardingVoteSink forwardingMethod;

    @Override
    public void onEnable() {
        NuVotifierBukkit.instance = this;

        // Set the plugin version.
        version = getDescription().getVersion();

        if (!getDataFolder().exists()) {
            getDataFolder().mkdir();
        }

        // Handle configuration.
        File config = new File(getDataFolder() + "/config.yml");
        YamlConfiguration cfg;
        File rsaDirectory = new File(getDataFolder() + "/rsa");

        /*
         * Use IP address from server.properties as a default for
         * configurations. Do not use InetAddress.getLocalHost() as it most
         * likely will return the main server address instead of the address
         * assigned to the server.
         */
        String hostAddr = Bukkit.getServer().getIp();
        if (hostAddr == null || hostAddr.length() == 0)
            hostAddr = "0.0.0.0";

        /*
         * Create configuration file if it does not exists; otherwise, load it
         */
        if (!config.exists()) {
            try {
                // First time run - do some initialization.
                getLogger().info("Configuring Votifier for the first time...");

                // Initialize the configuration file.
                config.createNewFile();

                // Load and manually replace variables in the configuration.
                String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")),
                        StandardCharsets.UTF_8);
                String token = TokenUtil.newToken();
                cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
                Files.write(cfgStr, config, StandardCharsets.UTF_8);

                /*
                 * Remind hosted server admins to be sure they have the right
                 * port number.
                 */
                getLogger().info("------------------------------------------------------------------------------");
                getLogger()
                        .info("Assigning NuVotifier to listen on port 8192. If you are hosting Craftbukkit on a");
                getLogger().info("shared server please check with your hosting provider to verify that this port");
                getLogger().info("is available for your use. Chances are that your hosting provider will assign");
                getLogger().info("a different port, which you need to specify in config.yml");
                getLogger().info("------------------------------------------------------------------------------");
                getLogger().info("Your default NuVotifier token is " + token + ".");
                getLogger().info("You will need to provide this token when you submit your server to a voting");
                getLogger().info("list.");
                getLogger().info("------------------------------------------------------------------------------");
            } catch (Exception ex) {
                getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
                gracefulExit();
                return;
            }
        }

        // Load configuration.
        cfg = YamlConfiguration.loadConfiguration(config);

        /*
         * Create RSA directory and keys if it does not exist; otherwise, read
         * keys.
         */
        try {
            if (!rsaDirectory.exists()) {
                rsaDirectory.mkdir();
                keyPair = RSAKeygen.generate(2048);
                RSAIO.save(rsaDirectory, keyPair);
            } else {
                keyPair = RSAIO.load(rsaDirectory);
            }
        } catch (Exception ex) {
            getLogger().log(Level.SEVERE, "Error reading configuration file or RSA tokens", ex);
            gracefulExit();
            return;
        }

        debug = cfg.getBoolean("debug", false);

        boolean setUpPort = cfg.getBoolean("enableExternal", true); //Always default to running the external port

        if (setUpPort) {
            // Load Votifier tokens.
            ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");

            if (tokenSection != null) {
                Map<String, Object> websites = tokenSection.getValues(false);
                for (Map.Entry<String, Object> website : websites.entrySet()) {
                    tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
                    getLogger().info("Loaded token for website: " + website.getKey());
                }
            } else {
                String token = TokenUtil.newToken();
                tokenSection = cfg.createSection("tokens");
                tokenSection.set("default", token);
                tokens.put("default", KeyCreator.createKeyFrom(token));
                try {
                    cfg.save(config);
                } catch (IOException e) {
                    getLogger().log(Level.SEVERE, "Error generating Votifier token", e);
                    gracefulExit();
                    return;
                }
                getLogger().info("------------------------------------------------------------------------------");
                getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
                getLogger().info("Your default Votifier token is " + token + ".");
                getLogger().info("You will need to provide this token when you submit your server to a voting");
                getLogger().info("list.");
                getLogger().info("------------------------------------------------------------------------------");
            }

            // Initialize the receiver.
            final String host = cfg.getString("host", hostAddr);
            final int port = cfg.getInt("port", 8192);
            if (debug)
                getLogger().info("DEBUG mode enabled!");

            final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
            if (disablev1) {
                getLogger().info("------------------------------------------------------------------------------");
                getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
                getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
                getLogger().info("------------------------------------------------------------------------------");
            }

            serverGroup = new NioEventLoopGroup(1);

            new ServerBootstrap().channel(NioServerSocketChannel.class).group(serverGroup)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel channel) throws Exception {
                            channel.attr(VotifierSession.KEY).set(new VotifierSession());
                            channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
                            channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
                            channel.pipeline().addLast("protocolDifferentiator",
                                    new VotifierProtocolDifferentiator(false, !disablev1));
                            channel.pipeline().addLast("voteHandler",
                                    new VoteInboundHandler(NuVotifierBukkit.this));
                        }
                    }).bind(host, port).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isSuccess()) {
                                serverChannel = future.channel();
                                getLogger()
                                        .info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
                            } else {
                                SocketAddress socketAddress = future.channel().localAddress();
                                if (socketAddress == null) {
                                    socketAddress = new InetSocketAddress(host, port);
                                }
                                getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress,
                                        future.cause());
                            }
                        }
                    });
        } else {
            getLogger().info(
                    "You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
        }

        ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
        if (forwardingConfig != null) {
            String method = forwardingConfig.getString("method", "none").toLowerCase(); //Default to lower case for case-insensitive searches
            if ("none".equals(method)) {
                getLogger().info(
                        "Method none selected for vote forwarding: Votes will not be received from a forwarder.");
            } else if ("pluginmessaging".equals(method)) {
                String channel = forwardingConfig.getString("pluginMessaging.channel", "NuVotifier");
                try {
                    forwardingMethod = new BukkitPluginMessagingForwardingSink(this, channel, this);
                    getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
                } catch (RuntimeException e) {
                    getLogger().log(Level.SEVERE,
                            "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
                }
            } else {
                getLogger().severe(
                        "No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
            }
        }
    }

    @Override
    public void onDisable() {
        // Shut down the network handlers.
        if (serverGroup != null) {
            if (serverChannel != null)
                serverChannel.close();
            serverGroup.shutdownGracefully();
        }
        if (forwardingMethod != null) {
            forwardingMethod.halt();
        }
        getLogger().info("Votifier disabled.");
    }

    private void gracefulExit() {
        getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
    }

    /**
     * Gets the instance.
     *
     * @return The instance
     */
    public static NuVotifierBukkit getInstance() {
        return instance;
    }

    /**
     * Gets the version.
     *
     * @return The version
     */
    public String getVersion() {
        return version;
    }

    public boolean isDebug() {
        return debug;
    }

    @Override
    public Map<String, Key> getTokens() {
        return tokens;
    }

    @Override
    public KeyPair getProtocolV1Key() {
        return keyPair;
    }

    @Override
    public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
        if (debug) {
            if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
                getLogger().info("Got a protocol v1 vote record -> " + vote);
            } else {
                getLogger().info("Got a protocol v2 vote record -> " + vote);
            }
        }
        Bukkit.getScheduler().runTask(this, new Runnable() {
            @Override
            public void run() {
                Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
            }
        });
    }

    @Override
    public void onError(Channel channel, Throwable throwable) {
        if (debug) {
            getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
        } else {
            getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
        }
    }

    @Override
    public void onForward(final Vote v) {
        if (debug) {
            getLogger().info("Got a forwarded vote -> " + v);
        }
        Bukkit.getScheduler().runTask(this, new Runnable() {
            @Override
            public void run() {
                Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
            }
        });
    }
}