bc9dd59a8455fbecf1580384f6c94055c5f29288
[vamsas.git] / src / uk / ac / vamsas / client / picking / SocketManager.java
1 package uk.ac.vamsas.client.picking;\r
2 \r
3 import java.net.*;\r
4 import java.util.*;\r
5 import java.util.logging.*;\r
6 \r
7 import org.apache.commons.logging.Log;\r
8 \r
9 /**\r
10  * Concrete implementation of the IPickManager interface that uses sockets for\r
11  * message communication. An instance of this class attempts to run the central\r
12  * server for other clients; failing that, it attempts to connect to an existing\r
13  * server instead.\r
14  */\r
15 public class SocketManager implements IPickManager\r
16 {\r
17         private Log logger = org.apache.commons.logging.LogFactory.getLog(uk.ac.vamsas.client.picking.SocketManager.class);\r
18         \r
19         // Maintains a list of client communication objects - each object represents\r
20         // a way of talking to either:\r
21         //  the server - if this is client side (and in which case, the list will only contain one element\r
22         //  the other clients - if this is server side\r
23         private LinkedList clients;\r
24         \r
25         private PickServer server;\r
26         \r
27         private IMessageHandler msgHandler;\r
28         \r
29         private boolean isRunning = true;\r
30         \r
31         /**\r
32          * Constructs a new PickManager. This method will return immediately, while\r
33          * a looping thread runs that attempts to run the server or connect to an\r
34          * existing server.\r
35          */\r
36         public SocketManager()\r
37         {\r
38         //logger.setLevel(Level.OFF);\r
39                 \r
40                 server = new PickServer(this);\r
41                 clients = new LinkedList();\r
42                 \r
43                 new InitializeThread().start();\r
44         }\r
45         \r
46         /**\r
47          * Registers a message handler with the manager that allows the manager to\r
48          * perform a method callback on that object whenever a message is received.\r
49          * @param handler the message handler to register\r
50          */\r
51         public void registerMessageHandler(IMessageHandler handler)\r
52         {\r
53                 msgHandler = handler;\r
54         }\r
55         \r
56         /**\r
57          * Attempts to establish a connection between two client endpoints. This\r
58          * method is called in two ways: 1) by the server when it receives a remote\r
59          * request (in which case the socket will already be established) and 2) by\r
60          * a client that is attempting to connect *to* the server.\r
61          * @param socket a socket endpoint for the connection\r
62          * @return true if the connection is successfully, false otherwise\r
63          */\r
64         synchronized boolean addEndPoint(Socket socket)\r
65         {\r
66                 PickEndPoint client = new PickEndPoint(this, socket);\r
67                 \r
68                 if (client.openConnection())\r
69                 {\r
70                         clients.add(client);\r
71                         //logger.info("List now contains " + clients.size() + " client(s)");\r
72                         return true;\r
73                 }\r
74                 \r
75                 return false;\r
76         }\r
77         \r
78         /**\r
79          * Sends a message to other clients.\r
80          * @param message the message to send\r
81          */\r
82         public void sendMessage(Message message)\r
83         {\r
84                 forwardMessage(null, message);\r
85         }\r
86         \r
87         /**\r
88          * Forwards (or sends) a message. When the server (client A) receives a\r
89          * message from client B, it must also forward it to clients C and D (etc),\r
90          * but mustn't forward it *back* to client B.\r
91          * @param origin the client endpoint that received the message (will be null\r
92          * if the message originates from this instance\r
93          * @param message the message to send\r
94          */\r
95         private void forwardMessage(PickEndPoint origin, Message message)\r
96         {\r
97                 for (int i = clients.size()-1; i >= 0; i--)\r
98                 {\r
99                         try\r
100                         {\r
101                                 PickEndPoint client = (PickEndPoint) clients.get(i);\r
102                                 if (client != origin)\r
103                                         client.send(message);\r
104                         }\r
105                         catch (Exception e)\r
106                         {\r
107                                 System.out.println("FORWARD: " + e);\r
108                         }\r
109                 }\r
110         }\r
111         \r
112         /**\r
113          * Handles a received message. If the manager is running in server mode,\r
114          * then it must ensure the message is also forwarded to the other clients.\r
115          * @param origin the client endpoint that received the message\r
116          * @message the message that was received\r
117          */\r
118         void processMessage(PickEndPoint origin, Message message)\r
119         {       \r
120                 if (server.isServer())\r
121                         forwardMessage(origin, message);\r
122                         \r
123                 if (msgHandler != null)\r
124                         msgHandler.handleMessage(message);\r
125 //              else\r
126 //                      logger.info("No handler available to deal with incoming message");\r
127         }\r
128         \r
129         /**\r
130          * Removes a client connection from the list when its connection is no\r
131          * longer valid.\r
132          * @param client the client endpoint to remove\r
133          */\r
134         synchronized void removeEndPoint(PickEndPoint client)\r
135         {\r
136                 clients.remove(client);\r
137                 //logger.info("List now contains " + clients.size() + " client(s)");\r
138                 \r
139                 // If there's no endpoints left, then we've lost all connections and\r
140                 // need to reinitialize - but only if we've not been told to stop\r
141                 if (clients.size() == 0 && isRunning)\r
142                         new InitializeThread().start();\r
143         }\r
144         \r
145         /**\r
146          * Thread extension class to handle the actual initialization\r
147          */\r
148         private class InitializeThread extends Thread\r
149         {\r
150                 public void run()\r
151                 {\r
152                         logger.debug("Initializing connection...");\r
153                         boolean connected = false;\r
154                         \r
155                         // Loop until we can get a connection (one way or the other)\r
156                         while (!connected && isRunning)\r
157                         {\r
158                                 // Sleep for a rnd time so we don't end up with all the VAMSAS\r
159                                 // apps trying to initialize servers at the same time\r
160                                 try { Thread.sleep((int)(10*Math.random())); }\r
161                                 catch (InterruptedException e) {}\r
162                                         \r
163                                 // Attempt to open the server port...\r
164                                 if (server.isServer() || server.createServer())\r
165                                         connected = true;\r
166 \r
167                                 // If it fails, then attempt to make a client connection...\r
168                                 else if (addEndPoint(null))\r
169                                         connected = true;\r
170                         }\r
171       logger.debug("Completed initializing connection.");\r
172                 }\r
173         }\r
174         \r
175         public void shutdown()\r
176         {\r
177                 logger.debug("Shutting down socket manager.");\r
178     if (server == null)\r
179                         throw new Error("Client Implementation Error: shutdown() called on uninitialized SocketManager.");\r
180                 \r
181                 isRunning = false;\r
182                 \r
183                 if (server.isServer())\r
184                         server.terminate();\r
185                 \r
186                 while (clients.size() > 0) {\r
187                         logger.debug("Closing endpoint.");\r
188       ((PickEndPoint)clients.getFirst()).terminate();\r
189                 }\r
190     logger.debug("Shutdown of socketmanager completed.");\r
191   }\r
192 }