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