package com.laytonsmith.core.federation; import java.io.IOException; import java.net.DatagramSocket; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map; /** * This class contains the POJO functionality for the Federation system. */ public class Federation { private static Federation defaultFederation; /** * Returns the default Federation object. If one isn't yet constructed, it * is constructed and returned. * * @return */ public static Federation GetFederation() { if (defaultFederation == null) { defaultFederation = new Federation(); } return defaultFederation; } /** * Clears the default Federation object. If {@link #GetFederation()} is * called after this, a new one will be constructed. */ public static void ClearFederation() { defaultFederation = null; } /** * Default master port */ public static final int MASTER_PORT = 55423; /** * Minimum slave server port number */ public static final int DYNAMIC_PORT_MINIMUM = 55424; /** * Maximum slave server port number */ public static final int DYNAMIC_PORT_MAXIMUM = 56423; /** * The timeout after which a server is considered "dead", that is, it's * heartbeat hasn't connected in this many seconds. */ public static final int DEAD_SERVER_TIMEOUT = 10; /** * The interval between heartbeats. */ public static final int HEARTBEAT_INTERVAL = 5; /** * When this is 0, the shutdown hooks will de-register with the * DaemonManager. */ private int serverCount = 0; private final Object serverCountLock = new Object(); private Map<String, FederationConnection> federationConnections = new HashMap<>(); private Map<String, FederationServer> federationServers = new HashMap<>(); private Socket masterSocket = null; // /** // * If the master socket dies, we need to establish a new one. This method // * checks to see if the master server for the specified master port is up // * and running, and if not, starts it. Regardless, the ServerSocket is // * returned, and it is guaranteed to be running and waiting for connections // * once this method returns. // * // * @param pn // * @param dm // * @param server_name If we end up needing to start a master socket, it does // * double duty as both the master socket and the slave socket. In that case, // * this is used, otherwise, it is ignored. // * @param master_port // * @return // */ // public synchronized void StartMasterSocket(final PersistenceNetwork pn, DaemonManager dm, String server_name, int master_port) { // if (!available(master_port)) { // //Something is already listening on this port, so we can assume the master socket is running. // return; // } // // Otherwise, it's not started, so start it, and then return. // final ServerSocket masterSocket; // try { // masterSocket = new ServerSocket(master_port); // AddShutdownHook(pn, dm, masterSocket, server_name); // // If we're the master, we need to set up the master server // // Now wait for connections, either from other servers requesting a port, or actual connections // // coming in for this server, which just happens to be a master server. // server.setServerSocket(masterSocket); // AddHeartbeatThread(pn, dm, server_name, masterSocket); // } catch (IOException ex) { // throw new ConfigRuntimeException(ex.getMessage(), Exceptions.ExceptionType.IOException, Target.UNKNOWN, ex); // } // // // Now start the master thread listening // new Thread(new Runnable() { // // @Override // public void run() { // try { // while (!masterSocket.isClosed()) { // Socket s = masterSocket.accept(); // HandleConnection(pn, s); // } // } catch (SocketException ex) { // // Not an error, the socket is just shutting down. // } catch (IOException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class.getName()).log(Level.SEVERE, null, ex); // } // } // }, Implementation.GetServerType().getBranding() + "-Federation-Allow-" + server_name + " (Master)").start(); // return masterSocket; // } // // public void AddHeartbeatThread(final PersistenceNetwork pn, final DaemonManager dm, final String server_name, final ServerSocket socket) { // new Thread(new Runnable() { // // @Override // @SuppressWarnings("SleepWhileInLoop") // public void run() { // while (!socket.isClosed()) { // // Just in case *our* master thread died, we want to restart it. // FederationServer us = null; // for (FederationServer s : federationServers.values()) { // if (s.server_name.equals(server_name)) { // us = s; // break; // } // } // // assert us != null; // // StartMasterSocket(pn, dm, server_name, us.master_port); // FederationRegistration reg; // try { // reg = FederationRegistration.fromJSON(pn.get(new String[]{"federation", server_name})); // reg.updateLastUpdated(); // pn.set(dm, new String[]{"federation", server_name}, reg.toJSON()); // } catch (DataSourceException | IllegalArgumentException | IOException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class.getName()).log(Level.SEVERE, null, ex); // } catch (ReadOnlyException ex) { // HandleReadOnlyException(ex); // } // try { // Thread.sleep(HEARTBEAT_INTERVAL); // } catch (InterruptedException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class // .getName()).log(Level.SEVERE, null, ex); // } // } // try { // // Socket is closed, remove ourselves from the registration database. // pn.clearKey(dm, new String[]{"federation", server_name}); // } catch (DataSourceException | IOException | IllegalArgumentException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class.getName()).log(Level.SEVERE, null, ex); // } catch (ReadOnlyException ex) { // HandleReadOnlyException(ex); // return; // } // } // }, Implementation.GetServerType().getBranding() + "-Federation-Heartbeat-" + server_name).start(); // // } // // private static void AddShutdownHook(final PersistenceNetwork pn, final DaemonManager dm, final ServerSocket masterSocket, final String server_name) { // StaticLayer.GetConvertor().addShutdownHook(new Runnable() { // // @Override // public void run() { // // Add a shutdown hook to kill the master server. // if (!masterSocket.isClosed()) { // try { // try { // masterSocket.close(); // // } catch (IOException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class // .getName()).log(Level.SEVERE, null, ex); // } // // pn.clearKey(dm, new String[]{"federation", server_name}); // } catch (DataSourceException | IOException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class // .getName()).log(Level.SEVERE, null, ex); // } catch (ReadOnlyException ex) { // HandleReadOnlyException(ex); // } finally { // synchronized (serverCountLock) { // serverCount--; // if (serverCount == 0) { // dm.deactivateThread(null); // } // } // } // } // } // }); // } // /** * Checks to see if a specific port is available. * * @param port the port to check for availability */ public static boolean available(int port) { if (port < 0 || port > 65535) { throw new IllegalArgumentException("Invalid start port: " + port); } ServerSocket ss = null; DatagramSocket ds = null; try { ss = new ServerSocket(port); ss.setReuseAddress(true); ds = new DatagramSocket(port); ds.setReuseAddress(true); return true; } catch (IOException e) { } finally { if (ds != null) { ds.close(); } if (ss != null) { try { ss.close(); } catch (IOException e) { /* should not be thrown */ } } } return false; } // // private static void RegisterServer(PersistenceNetwork pn, DaemonManager dm, String serverName, int port, boolean is_master) throws DataSourceException, ReadOnlyException, IOException { // FederationRegistration reg = new FederationRegistration(serverName, is_master, port); // pn.set(dm, new String[]{"federation", serverName}, reg.toJSON()); // } // // @SuppressWarnings("ConvertToStringSwitch") // private static void HandleConnection(PersistenceNetwork pn, Socket s) throws IOException { // try ( // BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8")); // PrintWriter out = new PrintWriter(s.getOutputStream(), true);) { // FederationVersion version; // try { // version = FederationVersion.fromVersion(reader.readLine()); // } catch (IllegalArgumentException ex) { // //Don't know the version it's reporting, so let's give up. // out.println("ERROR"); // out.println(ex.getMessage()); // out.close(); // s.close(); // return; // } // //Main command // String header = reader.readLine(); // if ("GET PORT".equals(header)) { // // This is a port request to the master port. // int newPort = -1; // portFinder: // while (true) { // // Generate a random port between the min and max values, and check in the // // registration to see if it is already in use. // Random r = new Random(); // newPort = r.nextInt(DYNAMIC_PORT_MAXIMUM - DYNAMIC_PORT_MINIMUM) + DYNAMIC_PORT_MINIMUM; // if (!available(newPort)) { // continue; // } // Map<String[], String> servers; // try { // servers = pn.getNamespace(new String[]{"federation"}); // } catch (DataSourceException | IllegalArgumentException ex) { // Logger.getLogger(com.laytonsmith.core.functions.Federation.class.getName()).log(Level.SEVERE, null, ex); // return; // } // for (String server : servers.values()) { // FederationRegistration reg = FederationRegistration.fromJSON(server); // if (reg.updatedSince(DEAD_SERVER_TIMEOUT * 1000)) { // if (reg.port == newPort) { // continue portFinder; // } // } // } // break; // } // out.println(newPort); // } else if ("HELLO".equals(header)) { // // We are establishing the connection. We need to read in several things, namely the connection // // information, and ensure that this connection is allowed by the server settings. (That is, compared // // against information provided in federation_remote_allow). // // Once we determine if this is a request for another connection, we will either respond and close, or // // if we are the master socket, and the request is for another server, or we will move the socket to // // another thread and return. Either way, we need to return quickly, so that the socket can accept more // // threads. We need the FederationServer object so we can grab the allow parameters. // FederationServer server = null; // for (FederationServer srv : federationServers.values()) { // if (srv.serverSocket.getLocalPort() == s.getLocalPort()) { // server = srv; // break; // } // } // assert server != null; // if (version == FederationVersion.V1_0_0) { // String server_name = reader.readLine(); // if (server.server_name.equals(server_name)) { // // This is the right server // } // } // } else if ("SCRIPT".equals(header)) { // // Protocol Version // if (version == FederationVersion.V1_0_0) { // int contentLength = Integer.parseInt(reader.readLine()); // char[] request = new char[contentLength]; // reader.read(request); // String sRequest = new String(request); // System.out.println(sRequest); // } // } // } // } // // private static void HandleReadOnlyException(ReadOnlyException ex) { // throw new ConfigRuntimeException("The \"federation\" namespace may not be set to read only." // + " Check you persistence config file and ensure that that namespace is read/write. " + ex.getMessage(), // Exceptions.ExceptionType.IOException, Target.UNKNOWN); // } }