--- /dev/null
+package uk.ac.vamsas.client.picking;\r
+\r
+/**\r
+ * Defines a custom message that applications can use to send message types that\r
+ * haven't been predefined in the API.\r
+ */\r
+public class CustomMessage extends Message\r
+{ \r
+ /**\r
+ * Constructs a new custom message.\r
+ * @param message the message to be sent\r
+ */\r
+ public CustomMessage(String message)\r
+ {\r
+ this.message = "CUSTOM_" + message;\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+package uk.ac.vamsas.client.picking;\r
+\r
+/**\r
+ * Interface that defines the methods required for a message handler. This\r
+ * interface must be implemented by the client application (TOPALi, Jalview or\r
+ * AstexViewer).\r
+ */\r
+public interface IMessageHandler\r
+{\r
+ /**\r
+ * Responds to a message received event.\r
+ * @param message the message that was received\r
+ */\r
+ public void handleMessage(Message message);\r
+}
\ No newline at end of file
--- /dev/null
+package uk.ac.vamsas.client.picking;\r
+\r
+/**\r
+ * Interface that defines the methods required for a pick manager.\r
+ */\r
+public interface IPickManager\r
+{\r
+ /**\r
+ * Sends a message.\r
+ * @param message the message to send\r
+ */\r
+ public void sendMessage(Message message);\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
+}
\ No newline at end of file
--- /dev/null
+package uk.ac.vamsas.client.picking;\r
+\r
+/**\r
+ * Abstract base class for all message types supported by the picking API.\r
+ */\r
+public abstract class Message\r
+{\r
+ protected String message;\r
+ \r
+ /**\r
+ * Constructs a new message.\r
+ */\r
+ protected Message()\r
+ {\r
+\r
+ }\r
+ \r
+ /**\r
+ * Returns the raw message content as a string.\r
+ * @return the raw message content as a string\r
+ */\r
+ public String getRawMessage()\r
+ { return message; }\r
+}
\ No newline at end of file
--- /dev/null
+package uk.ac.vamsas.client.picking;\r
+\r
+/**\r
+ * Message class that can be used to send mouse over events.\r
+ */\r
+public class MouseOverMessage extends Message\r
+{\r
+ private String vorbaID;\r
+ private int position;\r
+ \r
+ /**\r
+ * Constructs a new mouse over message.\r
+ * @param vorbaID the VAMSAS object ID of the event's source (usually an\r
+ * alignment or alignment sequence)\r
+ * @param position a position on the source in its coordinate system (ie a\r
+ * column or nucleotide/residue position)\r
+ */\r
+ public MouseOverMessage(String vorbaID, int position)\r
+ {\r
+ this.vorbaID = vorbaID;\r
+ this.position = position;\r
+ \r
+ message = "MOUSEOVER_"\r
+ + "vorbaID=" + vorbaID + "_" + "position=" + position;\r
+ }\r
+ \r
+ /**\r
+ * Constructs a new mouse over message from its underlying string format.\r
+ * @param str the string representation of an instance of this object\r
+ * @throws java.lang.Exception if the message cannot be reconstructed\r
+ */\r
+ MouseOverMessage(String str)\r
+ throws Exception\r
+ {\r
+ String[] elements = str.split("_");\r
+ \r
+ }\r
+}
\ No newline at end of file
\r
import java.io.*;\r
import java.net.*;\r
+import java.util.logging.*;\r
\r
class PickEndPoint extends Thread\r
{\r
+ private Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\r
+ \r
private Socket socket;\r
private int rPort;\r
private BufferedWriter os;\r
private BufferedReader in;\r
\r
- private PickManager manager; \r
+ private SocketManager manager; \r
\r
- PickEndPoint(PickManager manager, Socket s)\r
+ PickEndPoint(SocketManager manager, Socket s)\r
{\r
this.manager = manager;\r
socket = s;\r
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));\r
\r
// Start the thread to listen for incoming messages\r
- System.out.println("CLIENT: connection successful to port "\r
+ logger.info("CLIENT: connection successful to port "\r
+ socket.getPort() + " via port " + socket.getLocalPort());\r
start();\r
\r
}\r
catch (Exception e)\r
{\r
- System.out.println("CLIENT: connection failed: " + e);\r
+ logger.info("CLIENT: connection failed: " + e);\r
return false;\r
}\r
}\r
\r
- void send(String str)\r
+ void send(Message message)\r
{\r
try\r
{\r
- System.out.println("CLIENT: send " + str + " to " + rPort);\r
+ String str = message.getRawMessage();\r
+ \r
+ logger.info("CLIENT: send " + str + " to " + rPort);\r
os.write(str);\r
\r
// We use a newline to terminate the message\r
}\r
catch (Exception e)\r
{\r
- System.out.println("CLIENT: failed to send");\r
+ logger.info("CLIENT: failed to send");\r
\r
// TODO: terminate the connection on a failed send or retry?\r
terminate();\r
while (true)\r
{\r
String str = in.readLine(); \r
- System.out.println("CLIENT: recv " + str + " from " + rPort);\r
+ logger.info("CLIENT: recv " + str + " from " + rPort);\r
+ \r
+ // TODO: Spawn this off into the GUI Event-Dispatch thread...\r
+ \r
+ // Convert the string back into something API friendly\r
+ Message message = strToMessage(str);\r
\r
- manager.handleMessage(this, str);\r
+ // Ignore corrupted or unknown message types\r
+ if (message != null) \r
+ manager.processMessage(this, message);\r
}\r
}\r
catch (Exception e)\r
{\r
// Means the other end of the connection has (probably died) so we need\r
// terminate this endpoint (if this is server side)\r
- System.out.println("CLIENT: read failed: " + e);\r
+ logger.info("CLIENT: read failed: " + e);\r
\r
terminate();\r
}\r
try { socket.close(); }\r
catch (IOException e) {}\r
\r
- System.out.println("CLIENT: closing connection to port " + socket.getPort());\r
+ logger.info("CLIENT: closing connection to port " + socket.getPort());\r
manager.removeEndPoint(this);\r
}\r
+ \r
+ private Message strToMessage(String str)\r
+ {\r
+ try\r
+ {\r
+ if (str.startsWith("CUSTOM_"))\r
+ return new CustomMessage(str.substring(7));\r
+ \r
+ if (str.startsWith("MOUSEOVER_"))\r
+ return new MouseOverMessage(str);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ logger.info("Unable to reconstruct message: " + e);\r
+ }\r
+ \r
+ return null;\r
+ }\r
}
\ No newline at end of file
*/\r
class PickServer extends Thread\r
{\r
- private static Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\r
+ private Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\r
\r
// The port number we'll listen on\r
static final int PORT = 53782;\r
// Set to true once the server is established\r
private boolean isServer = false;\r
\r
- private PickManager manager;\r
+ private SocketManager manager;\r
\r
/**\r
* Constructs a new instance of the server (but doesn't start it).\r
* @param manager a reference to the pick manager that owns this server\r
*/\r
- PickServer(PickManager manager)\r
+ PickServer(SocketManager manager)\r
{\r
this.manager = manager;\r
}\r
*/\r
public void run()\r
{\r
- logger.fine("SERVER: listening on " + PORT + " - SERVER");\r
+ logger.info("SERVER: listening on " + PORT + " - SERVER");\r
\r
// Loop forever, accepting connectons from other clients\r
// TODO: add in the ability to terminate the server if a VAMSAS session\r
try\r
{\r
Socket socket = serverSocket.accept(); \r
- logger.fine("SERVER: connection detected");\r
+ logger.info("SERVER: connection detected");\r
\r
manager.addEndPoint(socket);\r
}\r
import java.util.logging.*;\r
\r
/**\r
- * Manager class that maintains a list of connected clients in addition to\r
- * attempting to run a server to listen for client connections. If the server\r
- * initialization fails, then an attempt to connect (as a client) to another JVM\r
- * that is already a server happens instead.\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 PickManager\r
+public class SocketManager implements IPickManager\r
{\r
- private static Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\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
\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 PickManager()\r
+ public SocketManager()\r
{\r
+// logger.setLevel(Level.OFF);\r
+ \r
server = new PickServer(this);\r
clients = new LinkedList();\r
\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
* @param socket a socket endpoint for the connection\r
* @return true if the connection is successfully, false otherwise\r
*/\r
- boolean addEndPoint(Socket socket)\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.fine("List now contains " + clients.size() + " client(s)");\r
+ logger.info("List now contains " + clients.size() + " client(s)");\r
return true;\r
}\r
\r
\r
/**\r
* Sends a message to other clients.\r
- * @param str the message to send\r
+ * @param message the message to send\r
*/\r
- public void sendMessage(String str)\r
+ public void sendMessage(Message message)\r
{\r
- forwardMessage(null, str);\r
+ forwardMessage(null, message);\r
}\r
\r
/**\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 str the message to send\r
+ * @param message the message to send\r
*/\r
- private void forwardMessage(PickEndPoint origin, String str)\r
+ private void forwardMessage(PickEndPoint origin, Message message)\r
{\r
ListIterator itor = clients.listIterator();\r
while (itor.hasNext())\r
PickEndPoint client = (PickEndPoint) itor.next();\r
\r
if (client != origin)\r
- client.send(str);\r
+ client.send(message);\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
- * @str the message that was received\r
+ * @message the message that was received\r
*/\r
- void handleMessage(PickEndPoint origin, String str)\r
- {\r
+ void processMessage(PickEndPoint origin, Message message)\r
+ { \r
if (server.isServer())\r
- forwardMessage(origin, str);\r
- \r
- // TODO: pass message to VAMSAS API\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
* longer valid.\r
* @param client the client endpoint to remove\r
*/\r
- void removeEndPoint(PickEndPoint client)\r
+ synchronized void removeEndPoint(PickEndPoint client)\r
{\r
clients.remove(client);\r
- logger.fine("List now contains " + clients.size() + " client(s)");\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
{\r
public void run()\r
{\r
- logger.fine("Initializing connection...");\r
+ logger.info("Initializing connection...");\r
boolean connected = false;\r
\r
// Loop until we can get a connection (one way or the other)\r
\r
import java.util.logging.*;\r
\r
-public class TestApp\r
+/**\r
+ * Simple example of a (runnable) class that shows how to use the picking API.\r
+ */\r
+public class TestApp implements IMessageHandler\r
{\r
- private static Logger logger = Logger.getLogger("uk.ac.vamsas.client.picking");\r
- \r
public static void main(String[] args)\r
throws Exception\r
{\r
-// logger.setLevel(Level.INFO);\r
- \r
TestApp app = new TestApp();\r
+ }\r
+ \r
+ public TestApp()\r
+ {\r
+ IPickManager manager = new SocketManager();\r
+ manager.registerMessageHandler(this);\r
\r
- PickManager manager = new PickManager();\r
- \r
- // Send 5 test messages...\r
-// for (int i = 0; i < 5; i++)\r
while (true)\r
{ \r
try { Thread.sleep((int) (Math.random()*20000)); }\r
catch (InterruptedException e) {}\r
\r
int rnd = (int) (Math.random()*100);\r
- manager.sendMessage("" + rnd);\r
+ CustomMessage msg = new CustomMessage("" + rnd);\r
+ \r
+ manager.sendMessage(msg);\r
}\r
}\r
\r
- public TestApp()\r
+ public void handleMessage(Message message)\r
{\r
+// System.out.println("Handler received " + message.getRawMessage());\r
}\r
}
\ No newline at end of file