package uk.ac.vamsas.client.picking; import java.net.*; import java.util.*; import java.util.logging.*; /** * Manager class that maintains a list of connected clients in addition to * attempting to run a server to listen for client connections. If the server * initialization fails, then an attempt to connect (as a client) to another JVM * that is already a server happens instead. */ public class PickManager { private static Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking"); // Maintains a list of client communication objects - each object represents // a way of talking to either: // the server - if this is client side (and in which case, the list will only contain one element // the other clients - if this is server side private LinkedList clients; private PickServer server; /** * Constructs a new PickManager. This method will return immediately, while * a looping thread runs that attempts to run the server or connect to an * existing server. */ public PickManager() { server = new PickServer(this); clients = new LinkedList(); new InitializeThread().start(); } /** * Attempts to establish a connection between two client endpoints. This * method is called in two ways: 1) by the server when it receives a remote * request (in which case the socket will already be established) and 2) by * a client that is attempting to connect *to* the server. * @param socket a socket endpoint for the connection * @return true if the connection is successfully, false otherwise */ boolean addEndPoint(Socket socket) { PickEndPoint client = new PickEndPoint(this, socket); if (client.openConnection()) { clients.add(client); logger.fine("List now contains " + clients.size() + " client(s)"); return true; } return false; } /** * Sends a message to other clients. * @param str the message to send */ public void sendMessage(String str) { forwardMessage(null, str); } /** * Forwards (or sends) a message. When the server (client A) receives a * message from client B, it must also forward it to clients C and D (etc), * but mustn't forward it *back* to client B. * @param origin the client endpoint that received the message (will be null * if the message originates from this instance * @param str the message to send */ private void forwardMessage(PickEndPoint origin, String str) { ListIterator itor = clients.listIterator(); while (itor.hasNext()) { PickEndPoint client = (PickEndPoint) itor.next(); if (client != origin) client.send(str); } } /** * Handles a received message. If the manager is running in server mode, * then it must ensure the message is also forwarded to the other clients. * @param origin the client endpoint that received the message * @str the message that was received */ void handleMessage(PickEndPoint origin, String str) { if (server.isServer()) forwardMessage(origin, str); // TODO: pass message to VAMSAS API } /** * Removes a client connection from the list when its connection is no * longer valid. * @param client the client endpoint to remove */ void removeEndPoint(PickEndPoint client) { clients.remove(client); logger.fine("List now contains " + clients.size() + " client(s)"); // If there's no endpoints left, then we've lost all connections and // need to reinitialize if (clients.size() == 0) new InitializeThread().start(); } /** * Thread extension class to handle the actual initialization */ private class InitializeThread extends Thread { public void run() { logger.fine("Initializing connection..."); boolean connected = false; // Loop until we can get a connection (one way or the other) while (!connected) { // Sleep for a rnd time so we don't end up with all the VAMSAS // apps trying to initialize servers at the same time try { Thread.sleep((int)Math.random()); } catch (InterruptedException e) {} // Attempt to open the server port... if (server.isServer() || server.createServer()) connected = true; // If it fails, then attempt to make a client connection... else if (addEndPoint(null)) connected = true; } } } }