/** * Copyright (C) 2014 zml (netevents@zachsthings.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zachsthings.netevents; import com.zachsthings.netevents.packet.EventPacket; import com.zachsthings.netevents.sec.AESSocketWrapper; import com.zachsthings.netevents.sec.SocketWrapper; import com.zachsthings.netevents.ping.PingListener; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.event.Event; import org.bukkit.plugin.java.JavaPlugin; import java.io.IOException; import java.io.Serializable; import java.net.SocketAddress; import java.util.*; import java.util.logging.Level; /** * Main class for NetEvents */ public class NetEventsPlugin extends JavaPlugin { /** * Number of event UUID's to keep to prevent duplicate events. Greater number potentially decreases duplicate events received. */ public static final int EVENT_CACHE_COUNT = 5000; private final LinkedList<UUID> processedEvents = new LinkedList<>(); private final Map<SocketAddress, Forwarder> forwarders = new HashMap<>(); private Receiver receiver; private PacketHandlerQueue handlerQueue; private ReconnectTask reconnectTask; private NetEventsConfig config; private ServerUUID uidHolder; private SocketWrapper socketWrapper; private boolean debugMode; @Override public void onEnable() { this.saveDefaultConfig(); getConfig().options().copyDefaults(true); uidHolder = new ServerUUID(getDataFolder().toPath().resolve("uuid.dat")); handlerQueue = new PacketHandlerQueue(this); handlerQueue.schedule(); reconnectTask = new ReconnectTask(); getServer().getScheduler().runTaskTimerAsynchronously(this, reconnectTask, 0, 20); reloadConfig(); if (config.getPassphrase().equals("changeme")) { getLogger().severe("Passphrase has not been changed from default! NetEvents will not enable until this happens"); getPluginLoader().disablePlugin(this); return; } socketWrapper = new AESSocketWrapper(config.getPassphrase()); try { connect(); } catch (IOException e) { getLogger().log(Level.SEVERE, "Error while connecting to remote servers. Are your addresses entered correctly?", e); getPluginLoader().disablePlugin(this); return; } getCommand("netevents").setExecutor(new StatusCommand(this)); debugMode = config.defaultDebugMode(); getServer().getPluginManager().registerEvents(new PingListener(this), this); } @Override public void onDisable() { try { close(); } catch (IOException e) { getLogger().log(Level.SEVERE, "Unable to properly disconnect network connections", e); } handlerQueue.cancel(); } @Override public void reloadConfig() { super.reloadConfig(); this.config = new NetEventsConfig(getConfig()); } /** * Reload the configuration for this plugin. * * @throws IOException When an error occurs while working with connections */ public void reload() throws IOException { close(); reloadConfig(); connect(); } private void close() throws IOException { if (receiver != null) { receiver.close(); receiver = null; } for (Iterator<Forwarder> it = forwarders.values().iterator(); it.hasNext();) { Forwarder conn = it.next(); it.remove(); conn.close(); } } private void connect() throws IOException { if (receiver == null) { receiver = new Receiver(this, config.getListenAddress()); receiver.bind(); } for (SocketAddress addr : config.getConnectAddresses()) { Forwarder fwd = new Forwarder(this); try { fwd.connect(addr); } catch (IOException ex) { reconnectTask.schedule(fwd); getLogger().log(Level.SEVERE, "Unable to connect to remote server " + addr + " (will keep trying): " + ex); } addForwarder(fwd); } } /** * Set whether debug logging is enabled. * * @see #debug(String) * @param debug The value to set debug logging to */ public void setDebugMode(boolean debug) { debugMode = debug; } /** * * @return Whether or not debug logging is enabled */ public boolean hasDebugMode() { return debugMode; } /** * Logs a debug message only if NetEvents has debug mode enabled ({@link #hasDebugMode()}). * * @param message The message to log */ protected void debug(String message) { if (hasDebugMode()) { getLogger().warning("[DEBUG] " + message); } } PacketHandlerQueue getHandlerQueue() { return handlerQueue; } ReconnectTask getReconnectTask() { return reconnectTask; } void addForwarder(Forwarder forwarder) { forwarders.put(forwarder.getRemoteAddress(), forwarder); } void removeForwarder(Forwarder f) { forwarders.remove(f.getRemoteAddress()); } /** * Returns a list of forwarders currently connected to this server. * @return Immutable list of currently connected forwarders */ public Collection<Forwarder> getForwarders() { return Collections.unmodifiableCollection(forwarders.values()); } /** * Returns a persistent unique ID for this server. * Useful for identifying this server in the network. * Persisted to {@code {@link #getDataFolder()}/uuid.dat}. * * @return a unique id for this server. */ public UUID getServerUUID() { return uidHolder.get(); } /** * Return the address this server is currently listening on for NetEvents connections. * * @return The listening address */ public SocketAddress getBoundAddress() { return receiver.getBoundAddress(); } /** * Calls the passed event on this server and forwards it to all * connected servers to be called remotely. * * Events must be serializable. * Remote servers will ignore events they do not know the class of. * * @param event The event to call * @param <T> The event type * @return The event (same as passed, just here for utility) */ public <T extends Event & Serializable> T callEvent(T event) { callEvent(new EventPacket(event), null); return event; } /** * Internal method to allow additional flexibility from events. * * @see {@link #callEvent(org.bukkit.event.Event)} to send events * @param packet The event packet to send * @param ignoreTo The forwarder to not send this packet to. This way we avoid */ public synchronized void callEvent(EventPacket packet, Forwarder ignoreTo) { while (processedEvents.size() > EVENT_CACHE_COUNT) { processedEvents.removeLast(); } if (processedEvents.contains(packet.getUid())) { return; } processedEvents.add(packet.getUid()); getServer().getPluginManager().callEvent(packet.getSendEvent()); for (Forwarder f : forwarders.values()) { if (ignoreTo != null && ignoreTo.equals(f)) { continue; } f.write(packet); } } public SocketWrapper getSocketWrapper() { return socketWrapper; } }