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.24
Committed: Thu Mar 15 01:05:46 2001 UTC (23 years, 2 months ago) by ajm
Branch: MAIN
Changes since 1.23: +4 -4 lines
Log Message:
The whole bally lot now is under uk.org.iscream ;p

File Contents

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