ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/conient/uk/org/iscream/cms/conient/ConnectionHandler.java
Revision: 1.14
Committed: Sat Feb 3 19:16:33 2001 UTC (23 years, 4 months ago) by ajm
Branch: MAIN
Changes since 1.13: +36 -4 lines
Log Message:
Now supports obtaining configuration

File Contents

# Content
1 //---PACKAGE DECLARATION---
2 package uk.ac.ukc.iscream.conient;
3
4 //---IMPORTS---
5 import uk.ac.ukc.iscream.util.*;
6 import java.io.*;
7 import java.net.*;
8 import javax.swing.JOptionPane;
9
10 /**
11 * This is the main thread for the client.
12 * Once started i continually checks it actionQueue
13 * for actions that other areas of the system have placed
14 * there, it then performs those actions.
15 *
16 * Currently this is the main thread where all non-GUI events
17 * are dispatched to.
18 *
19 * @author $Author: ajm4 $
20 * @version $Id: ConnectionHandler.java,v 1.13 2001/01/30 02:12:51 ajm4 Exp $
21 */
22 public class ConnectionHandler extends Thread {
23
24 //---FINAL ATTRIBUTES---
25
26 /**
27 * The current CVS revision of this class
28 */
29 public final String REVISION = "$Revision: 1.13 $";
30
31
32 /**
33 * The hardcoded protocol version that we are using.
34 * Used when handshaking with the server
35 */
36 public final double PROTOCOL_VERSION = 1.0;
37
38 /**
39 * Thread action DONOTHING.
40 * This is an invalid action, but here for completeness
41 * if anything sends this action it will warn appropriately
42 * but do what it says, ie, nothing ;)
43 */
44 public static final int DONOTHING = 0;
45
46 /**
47 * Thread action CONNECT.
48 * Opens the control link to the i-scream server.
49 */
50 public static final int CONNECT = 1;
51
52 /**
53 * Thread action STARTDATA.
54 * Opens the data link to the i-scream server
55 * and prepares all gui data components for data
56 * it then starts all relavant threads going.
57 */
58 public static final int STARTDATA = 2;
59
60 /**
61 * Thread action STOPDATA.
62 * Closes the data link and shuts down all components
63 * that update gui for various things.
64 */
65 public static final int STOPDATA = 3;
66
67 /**
68 * Thread action DISCONNECT.
69 * Checks to see if STOPDATA has been called if not,
70 * it will add STOPDATA and then DISCONNECT to
71 * the action queue and return. If STOPDATA has been
72 * called it closes down the Control Link and tidies up
73 * all relevant threads
74 */
75 public static final int DISCONNECT = 4;
76
77 /**
78 * Thread action QUIT.
79 * Checks the status of the two links, if either
80 * are still up, it queues the appropriate commands
81 * to close them down. It then System.exit(0)'s!
82 */
83 public static final int QUIT = 5;
84
85 /**
86 * Thread action GETCONFIGURATION
87 * Starts the command to obtain the configuration
88 * from the server.
89 * It then passes the IO to the Configuration object
90 * so it can obtain any specific configuration
91 */
92 public static final int GETCONFIGURATION = 6;
93
94 /**
95 * This is the time in seconds that this class
96 * should wait on the DataReader class to fully
97 * shutdown after calling shutdown() before
98 * forcing a shutdown
99 */
100 public static final int DATAREADER_SHUTDOWN_TIMEOUT = 5;
101
102 /**
103 * The default local server to connect to when a
104 * firewall is in use if one is not specified in the server
105 */
106 public static final String DEFAULT_FIREWALL_SERVER = "localhost";
107
108 /**
109 * The default time in seconds to wait for the
110 * firewall setup command to execute
111 */
112 public static final int DEFAULT_FIREWALL_COMMANDWAIT = 5;
113
114 //---STATIC METHODS---
115
116 //---CONSTRUCTORS---
117
118 /**
119 * Constructs new data handler.
120 * Needs a reference to the data panel so that
121 * it can set it processing inBound data
122 * Also gets a reference to the queue that will be
123 * used by other areas of the system to send it actions
124 *
125 * @param data the DataPanel in use
126 * @param actionQueue the actionQueue for this class
127 */
128 public ConnectionHandler(DataPanel data, Queue actionQueue) {
129 _data = data;
130 _actionQueue = actionQueue;
131 _myQueue = _actionQueue.getQueue();
132 }
133
134 //---PUBLIC METHODS---
135
136
137 /**
138 * Starts this ConnectionHandler running.
139 * This basically runs until told to stop, it gets "actions"
140 * from its actionQueue and switch's on them do determine
141 * what it should do it then carries out the action
142 *
143 * For details on what each action does see the action
144 * types.
145 */
146 public void run() {
147 if(_configuration.getProperty("control.onstartconnect") != null) {
148 _actionQueue.add(new Integer(CONNECT));
149 }
150 if(_configuration.getProperty("data.onstartconnect") != null) {
151 _actionQueue.add(new Integer(STARTDATA));
152 }
153 while(_running) {
154 // we wait for a call...
155 int action = 0;
156 try {
157 action = ((Integer) _actionQueue.get(_myQueue)).intValue();
158 } catch (InvalidQueueException e) {
159 // we 're never going to get this
160 // but if we do we should do something nasty
161 throw new RuntimeException("unable to retrieve events from actionQueue!");
162 }
163
164 // examine our action...
165 // if it was to connect...then we connect...
166 switch(action) {
167 case CONNECT:
168 if (_controlLink == null) {
169 try {
170 // get the server name from the config
171 _configuredServer = _configuration.getProperty("control.server");
172
173 if (_configuredServer == null) {
174 throw new IOException("no i-scream server in current configuration");
175 }
176
177 // open the socket to the server and bind the IO
178 // get the port from the config
179 String portString = _configuration.getProperty("control.port");
180 if (portString == null) {
181 throw new IOException("no i-scream server port in current configuration");
182 }
183 int port = 0;
184 try {
185 port = Integer.parseInt(portString);
186 } catch (NumberFormatException e) {
187 throw new IOException("no valid i-scream server port in current configuration");
188 }
189
190 // start firewall if needed
191 _server = handleFirewall(_configuredServer, port, _controlFirewallProcess);
192
193 Conient.setControlStatus("Connecting to - " + _server);
194 _controlLink = new Socket(_server, port);
195 _inBound = new BufferedReader(new InputStreamReader(_controlLink.getInputStream()));
196 _outBound = new PrintWriter(_controlLink.getOutputStream());
197 Conient.setControlStatus("Connection Established - " + _server);
198
199 String response;
200 response = _inBound.readLine();
201
202 // check the servers Protocol Identity against our own
203 // we SHOULD be backwards compatible, so we can continue if
204 // they are using a newer protocol, anything else then we die
205 if (!(Double.parseDouble(response.substring(10, response.length())) > PROTOCOL_VERSION)) {
206 Conient.addMessage("WARNING{control link}: server is using a newer protocol (" + response + "), please update your client, continuing with old protocol (PROTOCOL " + PROTOCOL_VERSION + ")" );
207 } else if (!(Double.parseDouble(response.substring(10, response.length())) < PROTOCOL_VERSION)) {
208 // tidy up
209 throw new IOException("incompatible protocol version");
210 }
211
212 // send the name of the client
213 _outBound.println(_configuration.getProperty("clientname"));
214 _outBound.flush();
215 response = _inBound.readLine();
216 if (!response.equals("OK")) {
217 // tidy up
218
219 throw new IOException("client name rejected - " + _configuration.getProperty("clientname"));
220 }
221 // get the config...we are connected now!
222 _actionQueue.add(new Integer(GETCONFIGURATION));
223 } catch (IOException e) {
224 // print the error and tidy up what's left
225 Conient.addMessage("ERROR{control link}: " + e);
226 _controlLink = null;
227 // and the firewall handler if there is one
228 if (_controlFirewallProcess != null) {
229 _controlFirewallProcess.destroy();
230 _controlFirewallProcess = null;
231 }
232 _actionQueue.clearQueue(_myQueue);
233 Conient.setControlStatus("Disconnected");
234 }
235 } else {
236 Conient.addMessage("WARNING{control link}: already established");
237 }
238 break;
239 case STARTDATA:
240 // as long as the data link hasn't been established
241 // we want to establish it
242 if (_dataLink == null) {
243 // check that the control link is open, if it isn't we
244 // might want to sort that problemo out
245 // we do this by simply queueing the event to occour, then
246 // this event to run again ;-)
247 if(_controlLink == null) {
248 Conient.addMessage("WARNING{data link}: control link not established - queueing start events");
249 _actionQueue.add(new Integer(CONNECT));
250 _actionQueue.add(new Integer(STARTDATA));
251 } else {
252 try {
253
254 // ask the server to start the data link
255 String response;
256 _outBound.println("STARTDATA");
257 _outBound.flush();
258 response = _inBound.readLine();
259
260 // see if the server suggested a good port
261 if (response.equals("ERROR")) {
262 throw new IOException("server unable to start data link at this time");
263 }
264 int port = 0;
265 try {
266 port = Integer.parseInt(response);
267 } catch (NumberFormatException e) {
268 throw new IOException("invalid data port suggested by server - " + response);
269 }
270
271 // start firewall if needed
272 _server = handleFirewall(_configuredServer, port, _dataFirewallProcess);
273 Conient.setDataStatus("Connecting to - " + _server + ":" + response);
274
275 _dataLink = new Socket(_server, port);
276
277 response = _inBound.readLine();
278 if (!response.equals("OK")) {
279 throw new IOException("server reported error establishing data channel");
280 }
281
282 // if the socket was ok, then we attack our IO hooks
283 _dataInBound = new BufferedReader(new InputStreamReader(_dataLink.getInputStream()));
284 _dataOutBound = new PrintWriter(_dataLink.getOutputStream());
285 Conient.setDataStatus("Connection Established - " + _server);
286 } catch (IOException e) {
287 // print the error and tidy up what's left
288 Conient.addMessage("ERROR{data link}: " + e);
289 _dataLink = null;
290 _actionQueue.clearQueue(_myQueue);
291 // and the firewall handler if there is one
292 if (_dataFirewallProcess != null) {
293 _dataFirewallProcess.destroy();
294 _dataFirewallProcess = null;
295 }
296 Conient.setDataStatus("Disconnected");
297 }
298 // now we want to start reading the data in
299 // so we start the appropriate components on their way
300 // we create a queue to give to both the reader and the
301 // displayer
302 Queue theQueue = new Queue();
303 _dataReader = new DataReader(_dataInBound, theQueue);
304 _data.setQueue(theQueue);
305 _data.cleanUpTabs();
306
307 // start the data rocking
308 new Thread(_data).start();
309 _dataReader.start();
310 // finished for us....
311 }
312 } else {
313 Conient.addMessage("WARNING{data link}: already established");
314 }
315 break;
316 case STOPDATA:
317 if(_dataLink != null) {
318 try {
319 String response;
320 // shut down the data link
321 Conient.setDataStatus("Disconnecting - " + _server);
322
323 // close the reader
324 _dataReader.shutdown();
325 // wait for it to close
326 boolean dirtyShutdown = true;
327 long startTime = System.currentTimeMillis();
328 while((System.currentTimeMillis() - startTime) < (DATAREADER_SHUTDOWN_TIMEOUT * 1000)) {
329 if (!_dataReader.isAlive()) {
330 dirtyShutdown = false;
331 break;
332 }
333 }
334 // warn if it didn't shutdown in time
335 if (dirtyShutdown) {
336 Conient.addMessage("WARNING{data link}: data reader thread did not close within timeout, killing its IO anyway!");
337 }
338
339 // tell the server
340 _outBound.println("STOPDATA");
341 _outBound.flush();
342 response = _inBound.readLine();
343
344 // check the server was ok with our request...
345 // even if it wasn't we will go anyway!
346 if (!response.equals("OK")) {
347 throw new IOException("server didn't OK request to stop data channel - stopping anyway");
348 }
349
350
351 // close the lot down
352 _dataInBound.close();
353 _dataOutBound.close();
354 _dataLink.close();
355 // get rid of the socket
356 _dataLink = null;
357
358 // and the firewall handler if there is one
359 if (_dataFirewallProcess != null) {
360 _dataFirewallProcess.destroy();
361 _dataFirewallProcess = null;
362 }
363
364 Conient.setDataStatus("Disconnected");
365 } catch (IOException e) {
366 // print the error and tidy up what's left
367 Conient.addMessage("ERROR{data link}: " + e);
368 try {
369 _dataOutBound.close();
370 _dataInBound.close();
371 _dataLink.close();
372 // and the firewall handler if there is one
373 if (_dataFirewallProcess != null) {
374 _dataFirewallProcess.destroy();
375 _dataFirewallProcess = null;
376 }
377 } catch (IOException e2) {
378 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
379 }
380 _dataLink = null;
381 _actionQueue.clearQueue(_myQueue);
382 Conient.setDataStatus("Disconnected");
383 }
384 } else {
385 Conient.addMessage("WARNING{data link}: already disconnected");
386 }
387 break;
388 case DISCONNECT:
389 if (_controlLink != null) {
390 if (_dataLink != null) {
391 // we want to tell ourselves to stop it
392 Conient.addMessage("WARNING{control link}: data link not disconnected - queueing stop events");
393 _actionQueue.add(new Integer(STOPDATA));
394 _actionQueue.add(new Integer(DISCONNECT));
395 } else {
396 try {
397 // request the server to disconnect
398 String response;
399 _outBound.println("DISCONNECT");
400 _outBound.flush();
401 response = _inBound.readLine();
402
403 // check the server was ok with our request...
404 // even if it wasn't we will go anyway!
405 if (!response.equals("OK")) {
406 throw new IOException("server didn't OK request to stop control channel - stopping anyway");
407 }
408
409 // then lets shutdown the link
410 Conient.setControlStatus("Disconnecting - " + _server);
411 _inBound.close();
412 _outBound.close();
413 _controlLink.close();
414 // for good measure
415 _controlLink = null;
416
417 // and the firewall handler if there is one
418 if (_controlFirewallProcess != null) {
419 _controlFirewallProcess.destroy();
420 _controlFirewallProcess = null;
421 }
422
423 Conient.setControlStatus("Disconnected");
424 } catch (IOException e) {
425 Conient.addMessage("ERROR{control link}: " + e);
426 try {
427 _inBound.close();
428 _outBound.close();
429 _controlLink.close();
430 // and the firewall handler if there is one
431 if (_controlFirewallProcess != null) {
432 _controlFirewallProcess.destroy();
433 _controlFirewallProcess = null;
434 }
435 } catch (IOException e2) {
436 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
437 }
438 _controlLink = null;
439 _actionQueue.clearQueue(_myQueue);
440 Conient.setControlStatus("Disconnected");
441 }
442 }
443 } else {
444 Conient.addMessage("WARNING{control link}: already disconnected");
445 }
446 break;
447 case QUIT:
448 Conient.addMessage("Exiting.");
449 try {
450 // stop data and control if data up
451 if (_dataLink != null) {
452 _actionQueue.add(new Integer(STOPDATA));
453 _actionQueue.add(new Integer(DISCONNECT));
454 _actionQueue.add(new Integer(QUIT));
455 throw new IOException();
456 }
457 // stop control
458 if (_controlLink != null) {
459 _actionQueue.add(new Integer(DISCONNECT));
460 _actionQueue.add(new Integer(QUIT));
461 throw new IOException();
462 }
463 Conient.addMessage("Finished.");
464 // go!
465 System.exit(0);
466 } catch (IOException e) {
467 Conient.addMessage("WARNING: open connections detected - queueing stop events");
468 }
469 break;
470 case GETCONFIGURATION:
471 if(_controlLink != null) {
472 try {
473 String response = null;
474 _outBound.println("STARTCONFIG");
475 _outBound.flush();
476 response = _inBound.readLine();
477 if (!response.equals("OK")) {
478 throw new IOException("server refused (" + response + ")");
479 }
480 _configuration.readServerConfiguration(_inBound, _outBound);
481 _outBound.println("ENDCONFIG");
482 _outBound.flush();
483 response = _inBound.readLine();
484 if (!response.equals("OK")) {
485 throw new IOException("server reported error when finishing configuration");
486 }
487 } catch (IOException e) {
488 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
489 }
490 }
491 break;
492 }
493 }
494 }
495
496 /**
497 * This method allows other classes
498 * to shutdown this connection handler.
499 */
500 public void shutdown() {
501 _running = false;
502 }
503
504 //---PRIVATE METHODS---
505
506 /**
507 * Handles opening pipes through firewalls.
508 * It checks the configuration for various entries
509 * to set up the link or not.
510 *
511 * If a link is setup it will return the name of the
512 * new server to connect to, if a link is NOT setup,
513 * it will simply return the original server name.
514 *
515 * The name of the machine that the pipe is setup on
516 * defaults to "localhost", unless the configuration
517 * specifies otherwise.
518 *
519 * Once it has run the firewall command it then waits
520 * a set period according to the config for the firewall
521 * pipe to be set up.
522 *
523 * The firewall process should be destroyed when the
524 * link is finished with.
525 *
526 * @param server the name of the server to open up the pipe to
527 * @param port the port number to open up the pipe to
528 * @param firewallProcess the holder for the new firewall process
529 * @return the server to connect to, as determined by this routine
530 */
531 private String handleFirewall(String server, int port, Process firewallProcess) {
532 String firewallCommand = _configuration.getProperty("firewall.command");
533 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
534 String firewallServer = _configuration.getProperty("firewall.server");
535 // if we are running firewall support...then lets start it
536 if (firewallCommand != null) {
537 // clean up the command with what we want
538 firewallCommand = replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
539 firewallCommand = replaceText(firewallCommand, "%SERVER%", server);
540 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
541 try {
542 // run the command
543 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
544
545 // work out how long we should wait before carrying on
546 int time = 0;
547 try {
548 time = Integer.parseInt(firewallCommandWait);
549 } catch (NumberFormatException e) {
550 time = 0;
551 }
552 if (time == 0) {
553 time = DEFAULT_FIREWALL_COMMANDWAIT;
554 }
555 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
556 // wait for the command to finish
557 try {
558 Thread.sleep(time * 1000);
559 } catch (InterruptedException e) {
560 }
561
562 // set the server we want to return
563 server = firewallServer;
564 if (server == null) {
565 server = DEFAULT_FIREWALL_SERVER;
566 }
567 } catch (IOException e) {
568 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
569 }
570 }
571 return server;
572 }
573
574 /**
575 * Searches a string and replaces all occurences
576 * of the given search text with the given replacement
577 * text.
578 *
579 * @param text the text to search and replace in
580 * @param search the string to look for and replace
581 * @param replace the text to replace with
582 * @return the updated version of text
583 */
584 private String replaceText(String text, String search, String replace) {
585 StringBuffer textBuffer = new StringBuffer(text);
586 int currIndex = 0;
587 currIndex = text.indexOf(search, currIndex);
588 while(currIndex != -1) {
589 if (currIndex != -1) {
590 textBuffer.delete(currIndex, currIndex + search.length());
591 textBuffer.insert(currIndex, replace);
592 }
593 text = textBuffer.toString();
594 currIndex = text.indexOf(search, currIndex + search.length());
595 }
596 return new String(textBuffer);
597 }
598 //---ACCESSOR/MUTATOR METHODS---
599
600 //---ATTRIBUTES---
601
602 /**
603 * The state if this thread
604 */
605 private boolean _running = true;
606
607 /**
608 * A reference to the displaying class DataPanel
609 */
610 private DataPanel _data;
611
612 /**
613 * The queue that actions are added to by other parts of the system
614 */
615 private Queue _actionQueue;
616
617 /**
618 * The queue number that we are reading from on the action queue
619 */
620 private int _myQueue;
621
622 /**
623 * The control link socket
624 */
625 private Socket _controlLink;
626
627 /**
628 * The data link socket
629 */
630 private Socket _dataLink;
631
632 /**
633 * The input for the control link
634 */
635 private BufferedReader _inBound;
636
637 /**
638 * The output for the control link
639 */
640 private PrintWriter _outBound;
641
642 /**
643 * The input for the data link
644 */
645 private BufferedReader _dataInBound;
646
647 /**
648 * The output for the data link
649 */
650 private PrintWriter _dataOutBound;
651
652 /**
653 * A reference to the DataReader in use
654 */
655 private DataReader _dataReader;
656
657 /**
658 * A reference to the system configuration component
659 */
660 private Configuration _configuration = Configuration.getInstance();
661
662 /**
663 * The server we will be connecting to
664 */
665 private String _server = null;
666
667 /**
668 * The server that the config says we should connect to
669 */
670 private String _configuredServer = null;
671
672 /**
673 * The process used to start the firewall pipe
674 * for the control link.
675 */
676 private Process _controlFirewallProcess = null;
677
678 /**
679 * The process used to start the firewall pipe
680 * for the data link.
681 */
682 private Process _dataFirewallProcess = null;
683
684 //---STATIC ATTRIBUTES---
685
686 }