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.20
Committed: Tue Feb 27 23:31:32 2001 UTC (23 years, 3 months ago) by ajm
Branch: MAIN
Changes since 1.19: +62 -6 lines
Log Message:
added support for 1.1 PROTOCOL
initial support for using it, though the configuration
of the actually host list is in GUI form, it currently doesn't work.
The option boxes of "discover" and "use host list" do though.
The configuration options for the list (if you want to hand edit) are:
hostList and knownHostList

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.19 2001/02/27 03:09:58 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.19 $";
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.1;
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").equals("1")) {
148 _actionQueue.add(new Integer(CONNECT));
149 }
150 if(_configuration.getProperty("data.onstartconnect").equals("1")) {
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 getConfigFromServer();
223 // set our host list if we know we have one we need to set
224 String hostList = _configuration.getProperty("hostList");
225 boolean hostListSet = false;
226 // send our hostList if we have
227 if (_configuration.getProperty("useHostList").equals("1")) {
228 if (hostList.equals("")) {
229 Conient.addMessage("WARNING{control link}: your host list is empty, the server will send ALL hosts");
230 }
231 hostListSet = setHostList(hostList);
232 // if not, indicate we want the lot
233 } else {
234 hostListSet = setHostList("");
235 }
236 // warn if there was a problem, it will have already error'd
237 if (!hostListSet) {
238 Conient.addMessage("WARNING{control link}: unable to set host list");
239 }
240 } catch (IOException e) {
241 // print the error and tidy up what's left
242 Conient.addMessage("ERROR{control link}: " + e);
243 _controlLink = null;
244 // and the firewall handler if there is one
245 closeFirewall(_controlFirewallProcess);
246 _actionQueue.clearQueue(_myQueue);
247 Conient.setControlStatus("Disconnected");
248 }
249 } else {
250 Conient.addMessage("WARNING{control link}: already established");
251 }
252 break;
253 case STARTDATA:
254 // as long as the data link hasn't been established
255 // we want to establish it
256 if (_dataLink == null) {
257 // check that the control link is open, if it isn't we
258 // might want to sort that problemo out
259 // we do this by simply queueing the event to occour, then
260 // this event to run again ;-)
261 if(_controlLink == null) {
262 Conient.addMessage("WARNING{data link}: control link not established - queueing start events");
263 _actionQueue.add(new Integer(CONNECT));
264 _actionQueue.add(new Integer(STARTDATA));
265 } else {
266 try {
267
268 // ask the server to start the data link
269 String response;
270 _outBound.println("STARTDATA");
271 _outBound.flush();
272 response = _inBound.readLine();
273
274 // see if the server suggested a good port
275 if (response.equals("ERROR")) {
276 throw new IOException("server unable to start data link at this time");
277 }
278 int port = 0;
279 try {
280 port = Integer.parseInt(response);
281 } catch (NumberFormatException e) {
282 throw new IOException("invalid data port suggested by server - " + response);
283 }
284
285 // start firewall if needed
286 _server = handleFirewall(_configuredServer, port, _dataFirewallProcess);
287 Conient.setDataStatus("Connecting to - " + _server + ":" + response);
288
289 _dataLink = new Socket(_server, port);
290
291 response = _inBound.readLine();
292 if (!response.equals("OK")) {
293 throw new IOException("server reported error establishing data channel");
294 }
295
296 // if the socket was ok, then we attack our IO hooks
297 _dataInBound = new BufferedReader(new InputStreamReader(_dataLink.getInputStream()));
298 _dataOutBound = new PrintWriter(_dataLink.getOutputStream());
299 Conient.setDataStatus("Connection Established - " + _server);
300 // now we want to start reading the data in
301 // so we start the appropriate components on their way
302 // we create a queue to give to both the reader and the
303 // displayer
304 Queue theQueue = new Queue();
305 _dataReader = new DataReader(_dataInBound, theQueue);
306 _data.setQueue(theQueue);
307 _data.cleanUpTabs();
308
309 // start the data rocking
310 new Thread(_data).start();
311 _dataReader.start();
312 // finished for us....
313 } catch (IOException e) {
314 // print the error and tidy up what's left
315 Conient.addMessage("ERROR{data link}: " + e);
316 _dataLink = null;
317 // and the firewall handler if there is one
318 closeFirewall(_dataFirewallProcess);
319 _actionQueue.clearQueue(_myQueue);
320 Conient.setDataStatus("Disconnected");
321 }
322 }
323 } else {
324 Conient.addMessage("WARNING{data link}: already established");
325 }
326 break;
327 case STOPDATA:
328 if(_dataLink != null) {
329 try {
330 String response;
331 // shut down the data link
332 Conient.setDataStatus("Disconnecting - " + _server);
333
334 // close the reader
335 _dataReader.shutdown();
336 // wait for it to close
337 boolean dirtyShutdown = true;
338 long startTime = System.currentTimeMillis();
339 while((System.currentTimeMillis() - startTime) < (DATAREADER_SHUTDOWN_TIMEOUT * 1000)) {
340 if (!_dataReader.isAlive()) {
341 dirtyShutdown = false;
342 break;
343 }
344 }
345 // warn if it didn't shutdown in time
346 if (dirtyShutdown) {
347 Conient.addMessage("WARNING{data link}: data reader thread did not close within timeout, killing its IO anyway!");
348 }
349
350 // tell the server
351 _outBound.println("STOPDATA");
352 _outBound.flush();
353 response = _inBound.readLine();
354
355 // check the server was ok with our request...
356 // even if it wasn't we will go anyway!
357 if (!response.equals("OK")) {
358 throw new IOException("server didn't OK request to stop data channel - stopping anyway");
359 }
360
361
362 // close the lot down
363 _dataInBound.close();
364 _dataOutBound.close();
365 _dataLink.close();
366 // get rid of the socket
367 _dataLink = null;
368
369 // and the firewall handler if there is one
370 closeFirewall(_dataFirewallProcess);
371
372 Conient.setDataStatus("Disconnected");
373 } catch (IOException e) {
374 // print the error and tidy up what's left
375 Conient.addMessage("ERROR{data link}: " + e);
376 try {
377 _dataOutBound.close();
378 _dataInBound.close();
379 _dataLink.close();
380 // and the firewall handler if there is one
381 closeFirewall(_dataFirewallProcess);
382 } catch (IOException e2) {
383 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
384 }
385 _dataLink = null;
386 _actionQueue.clearQueue(_myQueue);
387 Conient.setDataStatus("Disconnected");
388 }
389 } else {
390 Conient.addMessage("WARNING{data link}: already disconnected");
391 }
392 break;
393 case DISCONNECT:
394 if (_controlLink != null) {
395 if (_dataLink != null) {
396 // we want to tell ourselves to stop it
397 Conient.addMessage("WARNING{control link}: data link not disconnected - queueing stop events");
398 _actionQueue.add(new Integer(STOPDATA));
399 _actionQueue.add(new Integer(DISCONNECT));
400 } else {
401 try {
402 // request the server to disconnect
403 String response;
404 _outBound.println("DISCONNECT");
405 _outBound.flush();
406 response = _inBound.readLine();
407
408 // check the server was ok with our request...
409 // even if it wasn't we will go anyway!
410 if (!response.equals("OK")) {
411 throw new IOException("server didn't OK request to stop control channel - stopping anyway");
412 }
413
414 // then lets shutdown the link
415 Conient.setControlStatus("Disconnecting - " + _server);
416 _inBound.close();
417 _outBound.close();
418 _controlLink.close();
419 // for good measure
420 _controlLink = null;
421
422 // and the firewall handler if there is one
423 closeFirewall(_controlFirewallProcess);
424
425 Conient.setControlStatus("Disconnected");
426 } catch (IOException e) {
427 Conient.addMessage("ERROR{control link}: " + e);
428 try {
429 _inBound.close();
430 _outBound.close();
431 _controlLink.close();
432 // and the firewall handler if there is one
433 closeFirewall(_controlFirewallProcess);
434 } catch (IOException e2) {
435 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
436 }
437 _controlLink = null;
438 _actionQueue.clearQueue(_myQueue);
439 Conient.setControlStatus("Disconnected");
440 }
441 }
442 } else {
443 Conient.addMessage("WARNING{control link}: already disconnected");
444 }
445 break;
446 case QUIT:
447 Conient.addMessage("Exiting.");
448 try {
449 // stop data and control if data up
450 if (_dataLink != null) {
451 _actionQueue.add(new Integer(STOPDATA));
452 _actionQueue.add(new Integer(DISCONNECT));
453 _actionQueue.add(new Integer(QUIT));
454 throw new IOException();
455 }
456 // stop control
457 if (_controlLink != null) {
458 _actionQueue.add(new Integer(DISCONNECT));
459 _actionQueue.add(new Integer(QUIT));
460 throw new IOException();
461 }
462 Conient.addMessage("Finished.");
463 // go!
464 System.exit(0);
465 } catch (IOException e) {
466 Conient.addMessage("WARNING: open connections detected - queueing stop events");
467 }
468 break;
469 case GETCONFIGURATION:
470 getConfigFromServer();
471 break;
472 }
473 }
474 }
475
476 /**
477 * This method allows other classes
478 * to shutdown this connection handler.
479 */
480 public void shutdown() {
481 _running = false;
482 }
483
484 //---PRIVATE METHODS---
485
486 /**
487 * This method performs the SETHOSTLIST command.
488 * This is run by the CONNECT command.
489 * It tells the server which hosts we are interested
490 * in, if "" is sent, this indicates we want ALL hosts.
491 *
492 * @param hostList the list of hosts as gained from the config
493 * @return if we succeeded in setting the host list
494 */
495 private boolean setHostList(String hostList) {
496 boolean success = false;
497 // must have a control link open
498 if (_controlLink != null) {
499 // data link must be closed (according to 1.1 PROTOCOL)
500 if(_dataLink == null) {
501 try {
502 String response = null;
503 _outBound.println("SETHOSTLIST");
504 _outBound.flush();
505 response = _inBound.readLine();
506 if (!response.equals("OK")) {
507 throw new IOException("server refused - data link possibly still open?");
508 }
509 _outBound.println(hostList);
510 _outBound.flush();
511 response = _inBound.readLine();
512 if (!response.equals("OK")) {
513 throw new IOException("server had trouble with our request");
514 }
515 success = true;
516 } catch (IOException e) {
517 Conient.addMessage("ERROR{control link}: when setting hostlist - " + e);
518 }
519 }
520 }
521 return success;
522 }
523
524
525 /**
526 * This method performs the STARTCONFIG command on
527 * the server. this method is called after a CONNECT,
528 * and when a GETCONFIGURATION command is called.
529 */
530 private void getConfigFromServer() {
531 if(_controlLink != null) {
532 try {
533 String response = null;
534 _outBound.println("STARTCONFIG");
535 _outBound.flush();
536 response = _inBound.readLine();
537 if (!response.equals("OK")) {
538 throw new IOException("server refused (" + response + ")");
539 }
540 _configuration.readServerConfiguration(_inBound, _outBound);
541 _outBound.println("ENDCONFIG");
542 _outBound.flush();
543 response = _inBound.readLine();
544 if (!response.equals("OK")) {
545 throw new IOException("server reported error when finishing configuration");
546 }
547 } catch (IOException e) {
548 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
549 }
550 }
551 }
552
553 /**
554 * Handles opening pipes through firewalls.
555 * It checks the configuration for various entries
556 * to set up the link or not.
557 *
558 * If a link is setup it will return the name of the
559 * new server to connect to, if a link is NOT setup,
560 * it will simply return the original server name.
561 *
562 * The name of the machine that the pipe is setup on
563 * defaults to "localhost", unless the configuration
564 * specifies otherwise.
565 *
566 * Once it has run the firewall command it then waits
567 * a set period according to the config for the firewall
568 * pipe to be set up.
569 *
570 * The firewall process should be destroyed when the
571 * link is finished with.
572 *
573 * @param server the name of the server to open up the pipe to
574 * @param port the port number to open up the pipe to
575 * @param firewallProcess the holder for the new firewall process
576 * @return the server to connect to, as determined by this routine
577 */
578 private String handleFirewall(String server, int port, Process firewallProcess) {
579 String firewallCommand = _configuration.getProperty("firewall.command");
580 String useFirewall = _configuration.getProperty("useFirewall");
581 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
582 String firewallServer = _configuration.getProperty("firewall.server");
583 // if we are running firewall support...then lets start it
584 if (useFirewall.equals("1")) {
585 // clean up the command with what we want
586 firewallCommand = replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
587 firewallCommand = replaceText(firewallCommand, "%SERVER%", server);
588 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
589 try {
590 // run the command
591 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
592
593 // work out how long we should wait before carrying on
594 int time = 0;
595 try {
596 time = Integer.parseInt(firewallCommandWait);
597 } catch (NumberFormatException e) {
598 time = 0;
599 }
600 if (time == 0) {
601 time = DEFAULT_FIREWALL_COMMANDWAIT;
602 }
603 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
604 // wait for the command to finish
605 try {
606 Thread.sleep(time * 1000);
607 } catch (InterruptedException e) {
608 }
609
610 // set the server we want to return
611 server = firewallServer;
612 if (server.equals("")) {
613 server = DEFAULT_FIREWALL_SERVER;
614 }
615 } catch (IOException e) {
616 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
617 }
618 }
619 return server;
620 }
621
622 /**
623 * Checks to see if the given firewall process
624 * has been created. If it has it calls destroy()
625 * on it.
626 *
627 * @param firewallProcess the process to check
628 */
629 private void closeFirewall(Process firewallProcess) {
630 if (firewallProcess != null) {
631 firewallProcess.destroy();
632 firewallProcess = null;
633 Conient.addMessage("WARNING{firewall}: firewall process destroyed");
634 }
635 }
636
637 /**
638 * Searches a string and replaces all occurences
639 * of the given search text with the given replacement
640 * text.
641 *
642 * @param text the text to search and replace in
643 * @param search the string to look for and replace
644 * @param replace the text to replace with
645 * @return the updated version of text
646 */
647 private String replaceText(String text, String search, String replace) {
648 StringBuffer textBuffer = new StringBuffer(text);
649 int currIndex = 0;
650 currIndex = text.indexOf(search, currIndex);
651 while(currIndex != -1) {
652 if (currIndex != -1) {
653 textBuffer.delete(currIndex, currIndex + search.length());
654 textBuffer.insert(currIndex, replace);
655 }
656 text = textBuffer.toString();
657 currIndex = text.indexOf(search, currIndex + search.length());
658 }
659 return new String(textBuffer);
660 }
661 //---ACCESSOR/MUTATOR METHODS---
662
663 //---ATTRIBUTES---
664
665 /**
666 * The state if this thread
667 */
668 private boolean _running = true;
669
670 /**
671 * A reference to the displaying class DataPanel
672 */
673 private DataPanel _data;
674
675 /**
676 * The queue that actions are added to by other parts of the system
677 */
678 private Queue _actionQueue;
679
680 /**
681 * The queue number that we are reading from on the action queue
682 */
683 private int _myQueue;
684
685 /**
686 * The control link socket
687 */
688 private Socket _controlLink;
689
690 /**
691 * The data link socket
692 */
693 private Socket _dataLink;
694
695 /**
696 * The input for the control link
697 */
698 private BufferedReader _inBound;
699
700 /**
701 * The output for the control link
702 */
703 private PrintWriter _outBound;
704
705 /**
706 * The input for the data link
707 */
708 private BufferedReader _dataInBound;
709
710 /**
711 * The output for the data link
712 */
713 private PrintWriter _dataOutBound;
714
715 /**
716 * A reference to the DataReader in use
717 */
718 private DataReader _dataReader;
719
720 /**
721 * A reference to the system configuration component
722 */
723 private Configuration _configuration = Configuration.getInstance();
724
725 /**
726 * The server we will be connecting to
727 */
728 private String _server = null;
729
730 /**
731 * The server that the config says we should connect to
732 */
733 private String _configuredServer = null;
734
735 /**
736 * The process used to start the firewall pipe
737 * for the control link.
738 */
739 private Process _controlFirewallProcess = null;
740
741 /**
742 * The process used to start the firewall pipe
743 * for the data link.
744 */
745 private Process _dataFirewallProcess = null;
746
747 //---STATIC ATTRIBUTES---
748
749 }