Picking API now has message classes and basic implementation of interfaces (for its...
[vamsas.git] / src / uk / ac / vamsas / client / picking / SocketManager.java
diff --git a/src/uk/ac/vamsas/client/picking/SocketManager.java b/src/uk/ac/vamsas/client/picking/SocketManager.java
new file mode 100644 (file)
index 0000000..f4f4ab7
--- /dev/null
@@ -0,0 +1,164 @@
+package uk.ac.vamsas.client.picking;\r
+\r
+import java.net.*;\r
+import java.util.*;\r
+import java.util.logging.*;\r
+\r
+/**\r
+ * Concrete implementation of the IPickManager interface that uses sockets for\r
+ * message communication. An instance of this class attempts to run the central\r
+ * server for other clients; failing that, it attempts to connect to an existing\r
+ * server instead.\r
+ */\r
+public class SocketManager implements IPickManager\r
+{\r
+       private Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\r
+       \r
+       // Maintains a list of client communication objects - each object represents\r
+       // a way of talking to either:\r
+       //  the server - if this is client side (and in which case, the list will only contain one element\r
+       //  the other clients - if this is server side\r
+       private LinkedList clients;\r
+       \r
+       private PickServer server;\r
+       \r
+       private IMessageHandler msgHandler;\r
+       \r
+       /**\r
+        * Constructs a new PickManager. This method will return immediately, while\r
+        * a looping thread runs that attempts to run the server or connect to an\r
+        * existing server.\r
+        */\r
+       public SocketManager()\r
+       {\r
+//             logger.setLevel(Level.OFF);\r
+               \r
+               server = new PickServer(this);\r
+               clients = new LinkedList();\r
+               \r
+               new InitializeThread().start();\r
+       }\r
+       \r
+       /**\r
+        * Registers a message handler with the manager that allows the manager to\r
+        * perform a method callback on that object whenever a message is received.\r
+        * @param handler the message handler to register\r
+        */\r
+       public void registerMessageHandler(IMessageHandler handler)\r
+       {\r
+               msgHandler = handler;\r
+       }\r
+       \r
+       /**\r
+        * Attempts to establish a connection between two client endpoints. This\r
+        * method is called in two ways: 1) by the server when it receives a remote\r
+        * request (in which case the socket will already be established) and 2) by\r
+        * a client that is attempting to connect *to* the server.\r
+        * @param socket a socket endpoint for the connection\r
+        * @return true if the connection is successfully, false otherwise\r
+        */\r
+       synchronized boolean addEndPoint(Socket socket)\r
+       {\r
+               PickEndPoint client = new PickEndPoint(this, socket);\r
+               \r
+               if (client.openConnection())\r
+               {\r
+                       clients.add(client);\r
+                       logger.info("List now contains " + clients.size() + " client(s)");\r
+                       return true;\r
+               }\r
+               \r
+               return false;\r
+       }\r
+       \r
+       /**\r
+        * Sends a message to other clients.\r
+        * @param message the message to send\r
+        */\r
+       public void sendMessage(Message message)\r
+       {\r
+               forwardMessage(null, message);\r
+       }\r
+       \r
+       /**\r
+        * Forwards (or sends) a message. When the server (client A) receives a\r
+        * message from client B, it must also forward it to clients C and D (etc),\r
+        * but mustn't forward it *back* to client B.\r
+        * @param origin the client endpoint that received the message (will be null\r
+        * if the message originates from this instance\r
+        * @param message the message to send\r
+        */\r
+       private void forwardMessage(PickEndPoint origin, Message message)\r
+       {\r
+               ListIterator itor = clients.listIterator();\r
+               while (itor.hasNext())\r
+               {\r
+                       PickEndPoint client = (PickEndPoint) itor.next();\r
+                       \r
+                       if (client != origin)\r
+                               client.send(message);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Handles a received message. If the manager is running in server mode,\r
+        * then it must ensure the message is also forwarded to the other clients.\r
+        * @param origin the client endpoint that received the message\r
+        * @message the message that was received\r
+        */\r
+       void processMessage(PickEndPoint origin, Message message)\r
+       {       \r
+               if (server.isServer())\r
+                       forwardMessage(origin, message);\r
+                       \r
+               if (msgHandler != null)\r
+                       msgHandler.handleMessage(message);\r
+               else\r
+                       logger.info("No handler available to deal with incoming message");\r
+       }\r
+       \r
+       /**\r
+        * Removes a client connection from the list when its connection is no\r
+        * longer valid.\r
+        * @param client the client endpoint to remove\r
+        */\r
+       synchronized void removeEndPoint(PickEndPoint client)\r
+       {\r
+               clients.remove(client);\r
+               logger.info("List now contains " + clients.size() + " client(s)");\r
+               \r
+               // If there's no endpoints left, then we've lost all connections and\r
+               // need to reinitialize\r
+               if (clients.size() == 0)\r
+                       new InitializeThread().start();\r
+       }\r
+       \r
+       /**\r
+        * Thread extension class to handle the actual initialization\r
+        */\r
+       private class InitializeThread extends Thread\r
+       {\r
+               public void run()\r
+               {\r
+                       logger.info("Initializing connection...");\r
+                       boolean connected = false;\r
+                       \r
+                       // Loop until we can get a connection (one way or the other)\r
+                       while (!connected)\r
+                       {\r
+                               // Sleep for a rnd time so we don't end up with all the VAMSAS\r
+                               // apps trying to initialize servers at the same time\r
+                               try { Thread.sleep((int)Math.random()); }\r
+                               catch (InterruptedException e) {}\r
+                                       \r
+                               // Attempt to open the server port...\r
+                               if (server.isServer() || server.createServer())\r
+                                       connected = true;\r
+\r
+                               // If it fails, then attempt to make a client connection...\r
+                               else if (addEndPoint(null))\r
+                                       connected = true;\r
+                       }\r
+               }\r
+       }\r
+}
\ No newline at end of file